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 -
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.
Bu erda aks ettirishga imkon beradigan asosiy ro'yxat:
Mening ierarxiyamda
Reflection API
- 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.
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 name
kirish modifikatori bilan belgilangan private
; biz unga sinfdan tashqarida kira olmaymiz; =>
uning qiymatini ololmaymiz. "Xo'sh, muammo nimada? - sen aytasan. " getter
Kirish modifikatorini qo'shing yoki o'zgartiring." Va siz haqsiz, lekin agar MyClass
u 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 private
maydoniga kirishga harakat qilaylik : name
MyClass
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. Class
va 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 private
ham protected
. Bizning vaziyatimizda bizni qiziqtirgan sohaning nomini bilamiz va biz usuldan foydalanishimiz mumkin getDeclaredField(String)
, bu erda String
kerakli 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 name
butunlay bizning nazoratimiz ostida! get(Object)
Uning qiymatini ob'ektga qo'ng'iroq qilish orqali olishingiz mumkin Field
, bu erda Object
bizning sinfimizning namunasi MyClass
. Biz uni o'zgartiramiz String
va 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/catch
va 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 MyClass
sinf 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 private
va 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. Method
Va biz foydalanadigan ob'ektni chaqirish uchun invoke(Оbject, Args)
, bu erda Оbject
ham 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
.
package
to'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 ClassLoader
va 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 . String
Qabul 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. newInstance
Va bu konstruktorni chaqirish uchun biz ushbu parametrlar uchun qiymatlarni belgilaydigan usuldan foydalanamiz . invoke
Qo'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 Singleton
merosxo'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!
GO TO FULL VERSION