Reflection API

All lectures for UZ purposes
Daraja, Dars
Mavjud

Reflection API nima uchun kerak?

Java'dagi refleksiya — bu ishlab chiquvchiga dastur ishlayotgan paytda sinflar, interfeyslar, maydonlar va metodlar haqida ma'lumot olish va o'zgarishlar kiritishga imkon beruvchi mexanizmdir, bunda ularning nomlarini bilish shart emas.

Reflection API sinflarni yangi nusxalarini yaratish, metodlarni chaqirish va maydonlarning qiymatlarini olish yoki o'rnatishga ham yordam beradi.

Keling, refleksiyaning barcha imkoniyatlarini bir ro'yxatga jamlaymiz:

  • Obyektning sinfini bilib olish/aniqlash
  • Sinfning modifikatorlari, maydonlari, metodlari, doimiylari, konstruktorlari va superklasslari haqida ma'lumot olish
  • Qaysi metodlar amalga oshirilayotgan interfeys/ interfeyslarga tegishli ekanligini aniqlash
  • Dastur bajarilmasidan oldin sinf nomi ma'lum bo'lmaganida sinf nusxasini yaratish
  • Obyektning maydoni qiymatini nomi bo'yicha olish va o'rnatish
  • Obyekt metodini nomi bo'yicha chaqirish

Refleksiya deyarli barcha zamonaviy Java texnologiyalarida qo'llaniladi va zamonaviy Java/Java EE framework'lari va kutubxonalarining asosini tashkil etadi, masalan,:

  • Spring — veb-ilovalar yaratish uchun framework
  • JUnit — testlash uchun framework

Agar dasturchi bunday mexanizmlarga hech qachon duch kelmagan bo'lsa, ehtimol, u savol berishi mumkin — bularning hammasi nima uchun kerak? Bu holda javob juda oddiy, lekin bir vaqtning o'zida juda noaniq: refleksiya tufayli biz ilovani o'zimiz va o'z kodimizga moslashtirish uchun moslashuvchanlikni sezilarli darajada oshiramiz.

Lekin har doim o'zining ijobiy va salbiy tomonlari bor. Shuning uchun salbiy tomonlari haqida biraz gaplashamiz:

  • Ilovaning xavfsizligi buzilishi. Refleksiya yordamida biz kodning bir qismiga kirishimiz mumkin, bu qismiga biz kirishmasligimiz kerak edi (enkapsulyatsiya buzilishi).
  • Tizim xavfsizligi cheklovlari. Refleksiya bajarilish vaqtida ruxsat talab qiladi, xavfsizlik menejeri boshqaradigan tizimlar uchun mavjud bo'lmagan ruxsatlar.
  • Past samaradorlik. Java'dagi refleksiya turlarni dinamik tarzda aniqlaydi, classpath'ni skanerlash orqali yuklanadigan sinfni topadi. Bu dasturning samaradorligini pasaytiradi.
  • Qo'llab-quvvatlashdagi murakkablik. Refleksiya yordamida yozilgan kodni o'qish va tuzatish qiyin. Bu kamroq moslashuvchan bo'ladi va uni qo'llab-quvvatlash qiyinroq.

Reflection API orqali sinflar bilan ishlash

Refleksiya operatsiyalari hammasi java.lang.Class obyekti bilan boshlanadi. Har bir obyekt turi uchun o'zgarmas java.lang.Class nusxasi yaratiladi, bu obyektning xususiyatlarini olish, yangi obyektlar yaratish, metodlarni chaqirish uchun metodlar taqdim etadi.

Keling, java.lang.Class bilan ishlash uchun asosiy metodlar ro'yxatini ko'rib chiqaylik:

Metod Amal
String getName(); Sinf nomini qaytaradi
int getModifiers(); Kirish modifikatorlarini qaytaradi
Package getPackage(); Paket haqida ma'lumot qaytaradi
Class getSuperclass(); Superklass haqida ma'lumot qaytaradi
Class[] getInterfaces(); Interfeyslar massivini qaytaradi
Constructor[] getConstructors(); Sinf konstruktorlari haqida ma'lumot qaytaradi
Fields[] getFields(); Sinf maydonlarini qaytaradi
Files getFiled(String fieldName); Sinfning ma'lum bir maydonini nomi bo'yicha qaytaradi
Method[] getMethods(); Metodlar massivini qaytaradi

Bu sinf va interfeyslar, maydonlar va metodlar haqida ma'lumot olish uchun asosiy metodlardir. Shuningdek, maydon qiymatlarini olish yoki o'rnatish imkonini beruvchi metodlar ham bor, sinfning private maydonlariga kirish imkonini beradi. Ularni keyinroq ko'rib chiqamiz.

Hozirda biz java.lang.Class olish haqida gaplashamiz. Buning uchun bizda uch xil usul mavjud.

1. Class.forName orqali

Yugurayotgan dasturning sinfini olish uchun forName (String className) metodidan foydalanish kerak.

Ushbu kod Refleksiya yordamida sinflarni yaratish imkoniyatini namoyish etadi. Biz ishlaydigan Person sinfini yarataylik:


package com.company;

public class Person {
    private int age;
    private String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Va bizning misolimizning ikkinchi qismi — refleksiya bilan kod:


public class TestReflection {
    public static void main(String[] args) {
        try {
            Class<?> aClass = Class.forName("com.company.Person");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Agar sinfning to'liq nomi ma'lum bo'lsa, bunday yondashuv mumkin. Keyin statik metod Class.forName() yordamida mos sinfni olish mumkin. Ushbu usulni primitiv turlar uchun ishlatib bo'lmaydi.

2. .class orqali

Agar tur mavjud bo'lsa, lekin nusxasi bo'lmasa, tur nomiga .class qo'shib sinfni olish mumkin. Bu primitiv tur uchun sinfni olishning eng oddiy usuli.


Class aClass = Person.class;

3. .getClass() orqali

Agar obyekt nusxasi mavjud bo'lsa, uning sinfini olishning eng oddiy usuli object.getClass() chaqirishdir.


Person person = new Person();
Class aClass = person.getClass();

Mazkur yondashuvlar orasidagi farq nimada?

Agar dastur kodini yozayotganda qaysi sinf obyekti bilan qiziqayotganingizni bilsangiz, A.class foydalanishingiz mumkin. Agar nusxasi yo'q bo'lsa, .classdan foydalanish kerak.

Sinf metodlarini olish

Sinfimiz metodlarini qaytaradigan metodlarni ko'rib chiqamiz: getDeclaredMethods() va getMethods().

getDeclaredMethods() o'z ichiga olgan metodlarni aks ettiruvchi Method turidagi obyektlarni o'z ichiga olgan massivni qaytaradi, bu obyekt sinf yoki interfeysning e'lon qilgan barcha metodlarini, shu jumladan ochiq, xususiy, sukut bo'yicha va protected metodlarni aks ettiradi, lekin meros qilib olingan metodlarni istisno qiladi.

getMethods() barcha ochiq metodlar sinf yoki interfeysning e'lon qilgan va meros qilib olingan metodlarini aks ettiruvchi Method turidagi obyektlarni o'z ichiga olgan massivni qaytaradi, shu jumladan sinf yoki interfeysning e'lon qilgan va superklasslar va superinterfeysdan meros qilib olingan metodlarni.

Ulardan har birini ko'rib chiqamiz.

getDeclaredMethods() bilan boshlaymiz. Quyida biz Numbers abstrakt sinf bilan ishlaymiz, bu sinf ikki metod orasidagi farqni yana bir bor tushunishga yordam beradi. Bizning metodlarni Method massivini List<String>ga aylantiruvchi statik metodimizni yozamiz:


import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class TestReflection {
    public static void main(String[] args) {
        final Method[] declaredMethods = Number.class.getDeclaredMethods();
        List<String> actualMethodNames = getMethodNames(declaredMethods);
        actualMethodNames.forEach(System.out::println);
    }

    private static List<String> getMethodNames(Method[] methods) {
        return Arrays.stream(methods)
                .map(Method::getName)
                .collect(Collectors.toList());
    }
}

Kod natijasi quyidagicha ko'rinadi:

byteValue
shortValue
intValue
longValue
floatValue
doubleValue

Bu Number sinf ichida e'lon qilingan metodlar. getMethods() nima qaytaradi? Misolda ikkita qatorda o'zgarish kiritamiz:


final Method[] methods = Number.class.getMethods();
List<String> actualMethodNames = getMethodNames(methods);

Va natijada biz quyidagi metodlar to'plamini ko'ramiz:

byteValue
shortValue
intValue
longValue
floatValue
doubleValue
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll

Hamma classlar Objectdan meros olgani uchun, bizning metodimiz Object sinfining ochiq metodlarini ham qaytardi.

Sinf maydonlarini olish

Sinf maydonlarini olish uchun getFields va getDeclaredFields metodlaridan foydalaniladi. LocalDateTime sinfi misolida ko'rib chiqamiz. Kodimizni qayta yozamiz:


import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class TestReflection {
    public static void main(String[] args) {
        final Field[] declaredFields = LocalDateTime.class.getDeclaredFields();
        List<String> actualFieldNames = getFieldNames(declaredFields);
        actualFieldNames.forEach(System.out::println);
    }

    private static List<String> getFieldNames(Field[] fields) {
        return Arrays.stream(fields)
                .map(Field::getName)
                .collect(Collectors.toList());
    }
}

Ushbu kodni bajarish natijasida LocalDateTime sinfining maydonlar to'plamini olamiz.

MIN
MAX
serialVersionUID
date
time

Metodlar bilan bo'lgani kabi, kodni biroz o'zgartirib qaraymiz:


final Field[] fields = LocalDateTime.class.getFields();
List<String> actualFieldNames = getFieldNames(fields);

Dastur chiqishi:

MIN
MAX

Endi esa metodlarimiz o'rtasidagi farqni ko'rib chiqamiz.

getDeclaredFields metodi Class obyekti tomonidan aks ettirilgan sinf yoki interfeys tomonidan e'lon qilingan barcha maydonlarni aks ettiruvchi Field obyektlari massivini qaytaradi.

getFields metodi Class obyekti tomonidan aks ettirilgan sinf yoki interfeys tomonidan e'lon qilingan barcha ochiq (public) maydonlarni aks ettiruvchi Field obyektlari massivini qaytaradi.

Endi esa LocalDateTime ichiga qaraymiz.

Sinfning MIN va MAX maydonlari ochiq, shuning uchun ular getFields orqali ko'rinadi. O'z navbatida date, time, serialVersionUID maydonlari private, demak ular getFields orqali ko'rinmaydi va ularni getDeclaredFields yordamida olishimiz mumkin. Shu tarzda biz private maydonlar uchun Field obyekti orqali kirish imkonini olishimiz mumkin.

Boshqa metodlar va ularning ta'rifi

Sinfimiz Classning ayrim metodlari haqida gaplashamiz, ya'ni:

Metod Amal
getModifiers Sinf modifikatorlarini olish
getPackage Sinf joylashgan paketni olish
getSuperclass Superklassni olish
getInterfaces Sinf amalga oshirgan interfeyslar massivini olish
getName Sinfning to'liq nomini olish
getSimpleName Sinf nomini olish

getModifiers()

Modifikatorlarga Class obyekti orqali kirish mumkin.

Modifikatorlar public, static, interface va hokazo kalit so'zlarni ifodalaydi. Modifikatorlarni getModifiers() metodi orqali olishimiz mumkin:


Class<Person> personClass = Person.class;
int classModifiers = personClass.getModifiers();

Natija int o'zgaruvchisida saqlanadi, bunda har bir modifikator — bu o'rnatilgan yoki o'chirilgan bayroq turi. Biz modifikatorlarni java.lang.reflect.Modifier sinfidagi metodlardan foydalanib tekshirishimiz mumkin:


import com.company.Person;
import java.lang.reflect.Modifier;

public class TestReflection {
    public static void main(String[] args) {
        Class<Person> personClass = Person.class;
        int classModifiers = personClass.getModifiers();

        boolean isPublic = Modifier.isPublic(classModifiers);
        boolean isStatic = Modifier.isStatic(classModifiers);
        boolean isFinal = Modifier.isFinal(classModifiers);
        boolean isAbstract = Modifier.isAbstract(classModifiers);
        boolean isInterface = Modifier.isInterface(classModifiers);

        System.out.printf("Class modifiers: %d%n", classModifiers);
        System.out.printf("Is public: %b%n", isPublic);
        System.out.printf("Is static: %b%n", isStatic);
        System.out.printf("Is final: %b%n", isFinal);
        System.out.printf("Is abstract: %b%n", isAbstract);
        System.out.printf("Is interface: %b%n", isInterface);
    }
}

Person sinfimiz qanday e'lon qilinganini eslang:


public class Person {
   …
}

Natijada ekranda quyidagi chiqishni olamiz:

Class modifiers: 1
Is public: true
Is static: false
Is final: false
Is abstract: false
Is interface: false

Agar sinfimizni abstrakt qilsak, quyidagi natijani olamiz:


public abstract class Person { … }

va quyidagi chiqish:

Class modifiers: 1025
Is public: true
Is static: false
Is final: false
Is abstract: true
Is interface: false

Bizning kirish modifikatorimiz o'zgardi, demak, Modifier sinfining statik metodlari orqali qaytariladigan ma'lumotlar ham o'zgardi.

getPackage()

Faqatgina sinfni bilish orqali biz paket haqida ma'lumot olishimiz mumkin:


Class<Person> personClass = Person.class;
final Package aPackage = personClass.getPackage();
System.out.println(aPackage.getName());

getSuperclass()

Sinf obyekti orqali uning superklassiga kirish mumkin:


public static void main(String[] args) {
    Class<Person> personClass = Person.class;
    final Class<? super Person> superclass = personClass.getSuperclass();
    System.out.println(superclass);
}

Natijada hammamizga ma'lum Object sinfini olamiz:


class java.lang.Object

Agar sinfimizning ota sinfi mavjud bo'lsa, biz aniqraqliya mana shu superklassni ko'ramiz:


package com.company;

class Human {
    // ba'zi ma'lumot
}

public class Person extends Human {
    private int age;
    private String name;

    // ba'zi ma'lumot
}

Va natijada bizda superklassimiz chiqadi:


class com.company.Human

getInterfaces()

Sinf tomonidan amalga oshirilgan interfeyslar ro'yxatini shu tarzda olish mumkin:


public static void main(String[] args) {
    Class<Person> personClass = Person.class;
    final Class<?>[] interfaces = personClass.getInterfaces();
    System.out.println(Arrays.toString(interfaces));
}

Va sinfimiz Personni amalyot uchun o'zgartirishni unutmaymiz:


public class Person implements Serializable { … }

Chiqarish:

[interface java.io.Serializable]

Sinf bir nechta interfeyslarni amalga oshirishi mumkin. Shunday qilib, Class obyektlar massivi qaytariladi. Java Reflection API da interfeyslar ham Class turidagi obyektlar bilan vakil qilingan.

Diqqat: Metod faqatgina berilgan sinf tomonidan amalga oshirilgan interfeyslarni qaytardi, superklasslarning emas. To'liq interfeyslar ro'yxatini olish uchun ushbu sinf va uning barcha oila qo'shilib amalga oshirilgan interfeyslarga murojaat qilish kerak.

getName() & getSimpleName() & getCanonicalName()

Primitiv uchun, joylashtirilgan sinf uchun, anonim sinf uchun va String uchun misol keltiramiz:


public class TestReflection {
    public static void main(String[] args) {
        printNamesForClass(int.class, "int class (primitiv)");
        printNamesForClass(String.class, "String.class (oddiy sinf)");
        printNamesForClass(java.util.HashMap.SimpleEntry.class,
                "java.util.HashMap.SimpleEntry.class (joylashtirilgan sinf)");
        printNamesForClass(new java.io.Serializable() {
                }.getClass(),
                "new java.io.Serializable(){}.getClass() (anonim ichki sinf)");
    }

    private static void printNamesForClass(final Class<?> clazz, final String label) {
        System.out.printf("%s:%n", label);
        System.out.printf("\tgetName()):\t%s%n", clazz.getName());
        System.out.printf("\tgetCanonicalName()):\t%s%n", clazz.getCanonicalName());
        System.out.printf("\tgetSimpleName()):\t%s%n", clazz.getSimpleName());
        System.out.printf("\tgetTypeName():\t%s%n%n", clazz.getTypeName());
    }
}

Dastur natijasi:

int class (primitiv):
getName()): int
getCanonicalName()): int
getSimpleName()): int
getTypeName(): int

String.class (oddiy sinf):
getName()): java.lang.String
getCanonicalName()): java.lang.String
getSimpleName()): String
getTypeName(): java.lang.String

java.util.HashMap.SimpleEntry.class (joylashtirilgan sinf):
getName()): java.util.AbstractMap$SimpleEntry
getCanonicalName()): java.util.AbstractMap.SimpleEntry
getSimpleName()): SimpleEntry
getTypeName(): java.util.AbstractMap$SimpleEntry

new java.io.Serializable(){}.getClass() (anonim ichki sinf):
getName()): TestReflection$1
getCanonicalName()): null
getSimpleName()):
getTypeName(): TestReflection$1

Endi dastur natijalarini ko'rib chiqamiz:

  • getName() obyekti nomini qaytaradi.

  • getCanonicalName() Java tilining spetsifikatsiyasida ko'rsatilganidek, asosiy sinfning kanonik nomini qaytaradi. Agar asosiy sinf kanonik nomga ega bo'lmasa (ya'ni, agar bu mahalliy yoki anonim sinf yoki kanonik nomga ega bo'lmagan kompyonent turi bo'lgan massiv bo'lsa), nullni qaytaradi.

  • getSimpleName() asosiy sinfning oddiy nomini qaytaradi, dastlabki kodda belgilanganidek. Agar asosiy sinf anonim bo'lsa, bo'sh qatorni qaytaradi.

  • getTypeName() ushbu tur nomi uchun ma'lumotli qatorni qaytaradi.

Izohlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION