JavaRush /Java blogi /Random-UZ /Reflection API. Reflektsiya. Java ning qorong'u tomoni
Oleksandr Klymenko
Daraja
Харків

Reflection API. Reflektsiya. Java ning qorong'u tomoni

Guruhda nashr etilgan
Salom, yosh Padavan. Ushbu maqolada men sizga java dasturchilari faqat umidsizdek tuyulgan vaziyatda foydalanadigan kuch haqida gapirib beraman. Shunday qilib, Java-ning qorong'u tomoni -Reflection API
Reflection API.  Reflektsiya.  Java-ning qorong'u tomoni - 1
Java-da aks ettirish Java Reflection API yordamida amalga oshiriladi. Bu qanday aks ettirish? Internetda ham mashhur bo'lgan qisqa va aniq ta'rif mavjud. Reflektsiya (kech lotincha reflexio - orqaga qaytish) - bu dasturning bajarilishi paytida uning ma'lumotlarini o'rganish mexanizmi. Reflection sizga sohalar, usullar va sinf konstruktorlari haqidagi ma'lumotlarni tekshirish imkonini beradi. Ko'zgu mexanizmining o'zi kompilyatsiya paytida mavjud bo'lmagan, ammo dasturni bajarish paytida paydo bo'lgan turlarni qayta ishlashga imkon beradi. Xatolar haqida hisobot berish uchun aks ettirish va mantiqiy izchil modelning mavjudligi to'g'ri dinamik kodni yaratishga imkon beradi. Boshqacha qilib aytganda, java-da aks ettirish qanday ishlashini tushunish siz uchun bir qator ajoyib imkoniyatlarni ochadi. Siz tom ma'noda sinflar va ularning tarkibiy qismlarini o'ynashingiz mumkin.
Reflection API.  Reflektsiya.  Java-ning qorong'u tomoni - 2
Bu erda aks ettirishga imkon beradigan asosiy ro'yxat:
  • Ob'ekt sinfini toping/aniqlang;
  • Sinf modifikatorlari, maydonlar, usullar, konstantalar, konstruktorlar va yuqori sinflar haqida ma'lumot olish;
  • Amalga oshirilgan interfeys/interfeyslarga qaysi usullar tegishli ekanligini aniqlang;
  • Sinfning namunasini yarating va dastur bajarilmaguncha sinf nomi noma'lum;
  • Ob'ekt maydonining qiymatini nomi bo'yicha olish va o'rnatish;
  • Ob'ekt usulini nomi bilan chaqiring.
Reflection deyarli barcha zamonaviy Java texnologiyalarida qo'llaniladi. Java platforma sifatida o'ylab ko'rmasdan shunday ulkan qabul qilinishiga erisha oladimi yoki yo'qligini tasavvur qilish qiyin. Katta ehtimol bilan men qila olmadim. Siz aks ettirishning umumiy nazariy g'oyasi bilan tanishdingiz, endi uning amaliy qo'llanilishiga o'taylik! Biz Reflection API ning barcha usullarini o'rganmaymiz, faqat amalda duch kelgan narsalarni o'rganamiz. Ko'zgu mexanizmi sinflar bilan ishlashni o'z ichiga olganligi sababli, biz oddiy sinfga ega bo'lamiz - MyClass:
public class MyClass {
   private int number;
   private String name = "default";
//    public MyClass(int number, String name) {
//        this.number = number;
//        this.name = name;
//    }
   public int getNumber() {
       return number;
   }
   public void setNumber(int number) {
       this.number = number;
   }
   public void setName(String name) {
       this.name = name;
   }
   private void printData(){
       System.out.println(number + name);
   }
}
Ko'rib turganimizdek, bu eng keng tarqalgan sinf. Parametrli konstruktor biron bir sababga ko'ra izohlanadi, biz bunga keyinroq qaytamiz. Agar siz sinf mazmunini diqqat bilan ko'rib chiqsangiz, ehtimol getter'a for the ' ning yo'qligini ko'rgansiz name. Maydonning o'zi namekirish modifikatori bilan belgilangan private; biz unga sinfdan tashqarida kira olmaymiz; =>uning qiymatini ololmaymiz. "Xo'sh, muammo nimada? - sen aytasan. " getterKirish modifikatorini qo'shing yoki o'zgartiring." Va siz haqsiz, lekin agar MyClassu kompilyatsiya qilingan aar kutubxonasida yoki tahrirlash ruxsatisiz boshqa yopiq modulda bo'lsa va amalda bu juda tez-tez sodir bo'ladi. Va ba'zi bir beparvo dasturchi shunchaki yozishni unutib qo'ydi getter. Fikrlash haqida eslash vaqti keldi! Keling, sinf privatemaydoniga kirishga harakat qilaylik : nameMyClass
public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; //no getter =(
   System.out.println(number + name);//output 0null
   try {
       Field field = myClass.getClass().getDeclaredField("name");
       field.setAccessible(true);
       name = (String) field.get(myClass);
   } catch (NoSuchFieldException | IllegalAccessException e) {
       e.printStackTrace();
   }
   System.out.println(number + name);//output 0default
}
Keling, bu erda nima bo'lganini aniqlaylik. Java tilida ajoyib sinf mavjud Class. U bajariladigan Java ilovasida sinflar va interfeyslarni ifodalaydi. Classva o'rtasidagi bog'lanishga tegmaymiz ClassLoader. bu maqolaning mavzusi emas. Keyinchalik, ushbu sinfning maydonlarini olish uchun siz usulni chaqirishingiz kerak getFields(), bu usul bizga sinfning barcha mavjud maydonlarini qaytaradi. Bu biz uchun mos emas, chunki bizning maydonimiz private, shuning uchun biz usuldan foydalanamiz.Ushbu getDeclaredFields()usul shuningdek, sinf maydonlarining massivini qaytaradi, lekin endi ikkalasi privateham protected. Bizning vaziyatimizda bizni qiziqtirgan sohaning nomini bilamiz va biz usuldan foydalanishimiz mumkin getDeclaredField(String), bu erda Stringkerakli maydonning nomi. Eslatma: getFields()va getDeclaredFields()ota-ona sinfining maydonlarini qaytarmang! Ajoyib, biz ob'ektimizga Field havolani oldik name. Chunki maydon публичным(ommaviy) emas edi, u bilan ishlash uchun ruxsat berilishi kerak. Usul setAccessible(true)bizga ishlashni davom ettirishga imkon beradi. Endi maydon namebutunlay bizning nazoratimiz ostida! get(Object)Uning qiymatini ob'ektga qo'ng'iroq qilish orqali olishingiz mumkin Field, bu erda Objectbizning sinfimizning namunasi MyClass. Biz uni o'zgartiramiz Stringva uni o'zgaruvchimizga tayinlaymiz name. Agar bizda to'satdan "a" bo'lmasa setter, biz nom maydoniga yangi qiymat o'rnatish uchun usuldan foydalanishimiz mumkin set:
field.set(myClass, (String) "new value");
Tabriklaymiz! Siz hozirgina aks ettirishning asosiy mexanizmini o'zlashtirib oldingiz va maydonga kira olasiz private! Blok try/catchva istisnolar turlariga e'tibor bering. IDE-ning o'zi ularning majburiy mavjudligini ko'rsatadi, ammo ularning nomi nima uchun bu erda ekanligini aniq ko'rsatadi. Davom etishga ruxsat! Siz sezganingizdek, bizda MyClasssinf ma'lumotlari haqida ma'lumotni ko'rsatish usuli allaqachon mavjud:
private void printData(){
       System.out.println(number + name);
   }
Lekin bu dasturchi bu yerda ham meros qoldirdi. Usul kirish modifikatori ostida privateva biz har safar chiqish kodini o'zimiz yozishimiz kerak edi. Bu tartibli emas, bizning aksimiz qayerda?... Quyidagi funksiyani yozamiz:
public static void printData(Object myClass){
   try {
       Method method = myClass.getClass().getDeclaredMethod("printData");
       method.setAccessible(true);
       method.invoke(myClass);
   } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
       e.printStackTrace();
   }
}
Bu erda protsedura maydonni olish bilan taxminan bir xil - biz kerakli usulni nom bilan olamiz va unga kirish huquqini beramiz. MethodVa biz foydalanadigan ob'ektni chaqirish uchun invoke(Оbject, Args), bu erda Оbjectham sinfning namunasi MyClass. Args- usul argumentlari - bizniki hech qanday yo'q. Endi biz ma'lumotni ko'rsatish uchun funktsiyadan foydalanamiz printData:
public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; //?
   printData(myClass); // outout 0default
   try {
       Field field = myClass.getClass().getDeclaredField("name");
       field.setAccessible(true);
       field.set(myClass, (String) "new value");
       name = (String) field.get(myClass);
   } catch (NoSuchFieldException | IllegalAccessException e) {
       e.printStackTrace();
   }
   printData(myClass);// output 0new value
}
Huray, endi biz sinfning shaxsiy usuliga kirishimiz mumkin. Ammo, agar usul hali ham argumentlarga ega bo'lsa va nima uchun sharhlangan konstruktor mavjud bo'lsa-chi? Hamma narsaning o'z vaqti bor. Boshidagi ta'rifdan ko'rinib turibdiki, aks ettirish sizga rejimda runtime(dastur ishlayotgan vaqtda) sinf misollarini yaratishga imkon beradi! Biz sinf ob'ektini ushbu sinfning to'liq nomi bilan yaratishimiz mumkin. To'liq malakali sinf nomi sinf nomi bo'lib, unga boradigan yo'l package.
Reflection API.  Reflektsiya.  Java-ning qorong'u tomoni - 3
Mening ierarxiyamda packageto'liq ism MyClass"" bo'ladi reflection.MyClass. Siz sinf nomini oddiy usulda ham bilib olishingiz mumkin (u sinf nomini satr sifatida qaytaradi):
MyClass.class.getName()
Keling, reflektsiya yordamida sinfning namunasini yarataylik:
public static void main(String[] args) {
   MyClass myClass = null;
   try {
       Class clazz = Class.forName(MyClass.class.getName());
       myClass = (MyClass) clazz.newInstance();
   } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
       e.printStackTrace();
   }
   System.out.println(myClass);//output created object reflection.MyClass@60e53b93
}
Java ilovasi ishga tushganda, JVM ga barcha sinflar yuklanmagan. Agar sizning kodingiz sinfga tegishli bo'lmasa MyClass, JVM ga sinflarni yuklash uchun mas'ul bo'lgan shaxs, ya'ni ClassLoader, uni hech qachon u erda yuklamaydi. Shuning uchun biz uni yuklashga majburlashimiz ClassLoaderva sinfimiz tavsifini turdagi o'zgaruvchi shaklida olishimiz kerak Class. Ushbu vazifani bajarish uchun biz tavsifi talab qilinadigan sinf nomini ko'rsatadigan forName(String)usul mavjud . StringQabul qilingandan so'ng Сlass, usul chaqiruvi newInstance()qaytib keladi Object, u xuddi shu tavsifga muvofiq yaratiladi. Bu ob'ektni sinfimizga olib kelish uchun qoladi MyClass. Ajoyib! Bu qiyin edi, lekin tushunarli deb umid qilaman. Endi biz bir satrdan tom ma'noda sinfning namunasini yaratishimiz mumkin! Afsuski, tavsiflangan usul faqat standart konstruktor bilan ishlaydi (parametrlarsiz). Argumentlar bilan usullarni va parametrli konstruktorlarni qanday chaqirish mumkin? Bizning konstruktorimizga izoh berish vaqti keldi. Kutilganidek, newInstance()u standart konstruktorni topa olmaydi va endi ishlamaydi. Keling, sinf misolini yaratishni qayta yozamiz:
public static void main(String[] args) {
   MyClass myClass = null;
   try {
       Class clazz = Class.forName(MyClass.class.getName());
       Class[] params = {int.class, String.class};
       myClass = (MyClass) clazz.getConstructor(params).newInstance(1, "default2");
   } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
       e.printStackTrace();
   }
   System.out.println(myClass);//output created object reflection.MyClass@60e53b93
}
Sinf konstruktorlarini olish uchun sinf tavsifidan usulni chaqiring getConstructors()va konstruktor parametrlarini olish uchun quyidagini chaqiring getParameterTypes():
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
   Class[] paramTypes = constructor.getParameterTypes();
   for (Class paramType : paramTypes) {
       System.out.print(paramType.getName() + " ");
   }
   System.out.println();
}
Shunday qilib, biz barcha konstruktorlarni va ularga barcha parametrlarni olamiz. Mening misolimda ma'lum, allaqachon ma'lum parametrlarga ega ma'lum bir konstruktorga qo'ng'iroq bor. newInstanceVa bu konstruktorni chaqirish uchun biz ushbu parametrlar uchun qiymatlarni belgilaydigan usuldan foydalanamiz . invokeQo'ng'iroq usullari uchun ham xuddi shunday bo'ladi . Savol tug'iladi: konstruktorlarni aks ettiruvchi chaqiruv qayerda foydali bo'lishi mumkin? Zamonaviy java texnologiyalari, boshida aytib o'tilganidek, Reflection APIsiz ishlamaydi. Misol uchun, DI (Dependency Injection), bu erda izohlar usullar va konstruktorlarni aks ettirish bilan birgalikda Android ishlanmalarida mashhur bo'lgan Dagger kutubxonasini tashkil qiladi. Ushbu maqolani o'qib chiqqandan so'ng, siz o'zingizni Reflection API mexanizmlarida ishonch bilan ma'rifatli deb hisoblashingiz mumkin. Ko'zgu javaning qorong'u tomoni deb bejiz aytilmagan. Bu OOP paradigmasini butunlay buzadi. Java-da inkapsulyatsiya ba'zi dastur komponentlarini boshqalarga kirishini yashirish va cheklash uchun xizmat qiladi. Shaxsiy modifikatordan foydalanib, biz ushbu maydonga kirish faqat ushbu maydon mavjud bo'lgan sinf ichida bo'lishini nazarda tutamiz, shu asosda dasturning keyingi arxitekturasini quramiz. Ushbu maqolada biz istalgan joyga borish uchun aks ettirishdan qanday foydalanishni ko'rib chiqdik. Arxitektura yechimi ko'rinishidagi yaxshi misol generativ dizayn naqshidir - Singleton. Uning asosiy g'oyasi shundan iboratki, dasturning butun faoliyati davomida ushbu shablonni amalga oshiradigan sinf faqat bitta nusxaga ega bo'lishi kerak. Bu standart kirish modifikatorini konstruktor uchun shaxsiy qilib belgilash orqali amalga oshiriladi. Va agar biron bir dasturchi o'z aksini topsa, bunday sinflarni yaratsa, bu juda yomon bo'ladi. Aytgancha, men yaqinda xodimimdan eshitgan juda qiziq savol bor: shablonni amalga oshiradigan sinfning Singletonmerosxo'rlari bo'lishi mumkinmi? Bu holatda hatto aks ettirish ham kuchsiz bo'lishi mumkinmi? Maqola bo'yicha fikr-mulohazalaringizni va sharhlarda javobingizni yozing, shuningdek, savollaringizni bering! Reflection API-ning haqiqiy kuchi Runtime Annotations bilan birgalikda keladi, ehtimol bu haqda biz kelajakda Java-ning qorong'u tomoni haqidagi maqolada gaplashamiz. E'tiboringiz uchun rahmat!
Izohlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION