JavaRush /Java Blogu /Random-AZ /Java-da Reflection - İstifadə Nümunələri

Java-da Reflection - İstifadə Nümunələri

Qrupda dərc edilmişdir
Gündəlik həyatda "əks" anlayışına rast gələ bilərsiniz. Adətən bu söz insanın özünü öyrənmək prosesinə aiddir. Proqramlaşdırmada onun oxşar mənası var - bu, proqram haqqında məlumatların tədqiqi, həmçinin onun icrası zamanı proqramın strukturunun və davranışının dəyişdirilməsi mexanizmidir. Burada vacib olan odur ki, kompilyasiya zamanı deyil, icra zamanı edilir. Bəs niyə kodu işləmə zamanı yoxlamaq lazımdır? Siz artıq görürsünüz:/ Reflectiondan istifadə nümunələri - 1Refleksiyanın ideyası bir səbəbdən dərhal aydın olmaya bilər: bu ana qədər siz həmişə işlədiyiniz sinifləri bilirdiniz. Məsələn, bir sinif yaza bilərsiniz Cat:
package learn.javarush;

public class Cat {

   private String name;
   private int age;

   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   public void sayMeow() {

       System.out.println("Meow!");
   }

   public void jump() {

       System.out.println("Jump!");
   }

   public String getName() {
       return name;
   }

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

   public int getAge() {
       return age;
   }

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

@Override
public String toString() {
   return "Cat{" +
           "name='" + name + '\'' +
           ", age=" + age +
           '}';
}

}
Siz bu haqda hər şeyi bilirsiniz, görürsünüz ki, hansı sahələr və üsullar var. AnimalŞübhəsiz ki , birdən-birə proqram digər heyvan siniflərinə ehtiyac duyarsa, rahatlıq üçün ümumi bir siniflə miras sistemi yarada bilərsiniz . Əvvəllər biz hətta bir ana obyekti keçə biləcəyiniz bir baytarlıq klinikası sinfi yaratdıq Animalvə proqram it və ya pişik olmasından asılı olaraq heyvanı müalicə edərdi. Bu tapşırıqlar çox sadə olmasa da, proqram kompilyasiya zamanı dərslər haqqında lazım olan bütün məlumatları öyrənir. main()Buna görə də, bir üsulda bir obyekti Catbaytarlıq klinikasının metodlarına ötürəndə proqram artıq bilir ki, bu, it deyil, pişikdir . İndi təsəvvür edək ki, qarşımızda başqa bir vəzifə var. Məqsədimiz kod analizatoru yazmaqdır. CodeAnalyzerTək metodla sinif yaratmalıyıq - void analyzeClass(Object o). Bu üsul olmalıdır:
  • obyektin ona hansı sinfə keçdiyini müəyyənləşdirin və konsolda sinfin adını göstərin;
  • bu sinfin bütün sahələrinin, o cümlədən şəxsi sahələrin adlarını müəyyənləşdirin və onları konsolda göstərin;
  • bu sinfin bütün metodlarının, o cümlədən şəxsi olanların adlarını müəyyənləşdirin və onları konsolda göstərin.
Bu kimi bir şey görünəcək:
public class CodeAnalyzer {

   public static void analyzeClass(Object o) {

       //Вывести название класса, к которому принадлежит an object o
       //Вывести названия всех переменных этого класса
       //Вывести названия всех методов этого класса
   }

}
İndi bu problemlə əvvəllər həll etdiyiniz digər problemlər arasında fərq görünür. Bu vəziyyətdə çətinlik ondan ibarətdir ki, nə siz, nə də proqram metoda dəqiq nəyin keçəcəyini bilmirsiniz analyzeClass(). Siz proqram yazırsınız, başqa proqramçılar ondan istifadə etməyə başlayacaqlar, kim bu metoda nəyisə ötürə bilər - istənilən standart Java sinfi və ya onların yazdığı istənilən sinif. Bu sinifdə istənilən sayda dəyişən və metod ola bilər. Başqa sözlə, bu halda bizim (və proqramımızın) hansı siniflərlə işləyəcəyimiz barədə heç bir fikrimiz yoxdur. Bununla belə, biz bu problemi həll etməliyik. Və burada standart Java kitabxanası köməyimizə gəlir - Java Reflection API. Reflection API güclü dil xüsusiyyətidir. Rəsmi Oracle sənədlərində deyilir ki, bu mexanizmdən yalnız nə etdiklərini çox yaxşı anlayan təcrübəli proqramçılar tərəfindən istifadə edilməsi tövsiyə olunur. Tezliklə birdən-birə bizə əvvəlcədən belə xəbərdarlıqlar edildiyini anlayacaqsınız :) Reflection API-dən istifadə etməklə nələrin edilə biləcəyinin siyahısını təqdim edirik:
  1. Bir obyektin sinfini tapın/müəyyən edin.
  2. Sinif dəyişdiriciləri, sahələr, metodlar, sabitlər, konstruktorlar və supersiniflər haqqında məlumat əldə edin.
  3. Hansı metodların həyata keçirilən interfeysə/interfeyslərə aid olduğunu öyrənin.
  4. Proqram icra olunana qədər sinif adı naməlum olduqda sinif nümunəsini yaradın.
  5. Obyekt sahəsinin dəyərini adına görə alın və təyin edin.
  6. Obyektin metodunu adı ilə çağırın.
Təsirli siyahı, hə? :) Diqqət edin:Kod analizatorumuza hansı sinif obyektini ötürməyimizdən asılı olmayaraq, əks etdirmə mexanizmi bütün bunları “tezliklə” edə bilir! Nümunələrlə Reflection API-nin imkanlarına nəzər salaq.

Bir obyektin sinfini necə tapmaq / müəyyən etmək olar

Əsaslardan başlayaq. Java-nın əks etdirmə mexanizminin giriş nöqtəsi Class. Bəli, həqiqətən gülməli görünür, amma əks etdirmənin məqsədi budur :) Bir sinifdən istifadə edərək Class, ilk növbədə metodumuza keçən hər hansı obyektin sinfini müəyyənləşdiririk. Gəlin bunu sınayaq:
import learn.javarush.Cat;

public class CodeAnalyzer {

   public static void analyzeClass(Object o) {
       Class clazz = o.getClass();
       System.out.println(clazz);
   }

   public static void main(String[] args) {

       analyzeClass(new Cat("Barsik", 6));
   }
}
Konsol çıxışı:

class learn.javarush.Cat
İki şeyə diqqət yetirin. Birincisi, biz qəsdən klassı Catayrıca paketə qoyuruq.İndi learn.javarush;görə bilərsiniz ki, o, getClass()sinfin tam adını qaytarır. İkincisi, dəyişənimizi adlandırdıq clazz. Bir az qəribə görünür. Təbii ki, o, “class” adlandırılmalıdır, lakin “class” Java dilində qorunan sözdür və kompilyator dəyişənlərin belə adlandırılmasına icazə verməyəcək. Mən ondan çıxmalıydım :) Yaxşı, pis başlanğıc deyil! İmkanlar siyahısında başqa nələrimiz var idi?

Sinif dəyişdiriciləri, sahələr, metodlar, sabitlər, konstruktorlar və supersiniflər haqqında məlumat əldə etmək

Bu artıq daha maraqlıdır! Cari sinifdə sabitlər və ana sinif yoxdur. Tamlıq üçün onları əlavə edək. Ən sadə ana sinfi yaradaq Animal:
package learn.javarush;
public class Animal {

   private String name;
   private int age;
}
Və sinifimizə Catmiras və bir sabit əlavə edək :Animal
package learn.javarush;

public class Cat extends Animal {

   private static final String ANIMAL_FAMILY = "Семейство кошачьих";

   private String name;
   private int age;

   //...остальная часть класса
}
İndi tam dəstimiz var! Gəlin əks olunma imkanlarını sınayaq :)
import learn.javarush.Cat;

import java.util.Arrays;

public class CodeAnalyzer {

   public static void analyzeClass(Object o) {
       Class clazz = o.getClass();
       System.out.println("Name класса: " + clazz);
       System.out.println("Поля класса: " + Arrays.toString(clazz.getDeclaredFields()));
       System.out.println("Родительский класс: " + clazz.getSuperclass());
       System.out.println("Методы класса: " +  Arrays.toString(clazz.getDeclaredMethods()));
       System.out.println("Конструкторы класса: " + Arrays.toString(clazz.getConstructors()));
   }

   public static void main(String[] args) {

       analyzeClass(new Cat("Barsik", 6));
   }
}
Konsolda əldə etdiyimiz budur:
Name класса: class learn.javarush.Cat
Поля класса: [private static final java.lang.String learn.javarush.Cat.ANIMAL_FAMILY, private java.lang.String learn.javarush.Cat.name, private int learn.javarush.Cat.age]
Родительский класс: class learn.javarush.Animal
Методы класса: [public java.lang.String learn.javarush.Cat.getName(), public void learn.javarush.Cat.setName(java.lang.String), public void learn.javarush.Cat.sayMeow(), public void learn.javarush.Cat.setAge(int), public void learn.javarush.Cat.jump(), public int learn.javarush.Cat.getAge()]
Конструкторы класса: [public learn.javarush.Cat(java.lang.String,int)]
Sinif haqqında çox ətraflı məlumat aldıq! Və təkcə ictimai haqqında deyil, həm də şəxsi hissələr haqqında. Diqqət edin: private-dəyişənlər də siyahıda göstərilir. Əslində, sinfin "təhlili" bu nöqtədə tamamlanmış hesab edilə bilər: indi metoddan istifadə edərək, analyzeClass()mümkün olan hər şeyi öyrənəcəyik. Ancaq bunlar əks etdirmə ilə işləyərkən əldə etdiyimiz bütün imkanlar deyil. Özümüzü sadə müşahidələrlə məhdudlaşdırmayaq və aktiv fəaliyyətə keçək! :)

Proqram icra edilməzdən əvvəl sinif adı məlum deyilsə, sinif nümunəsini necə yaratmaq olar

Defolt konstruktordan başlayaq. Bu hələ bizim sinifdə deyil Cat, ona görə də əlavə edək:
public Cat() {

}
Catreflection (metod) istifadə edərək obyekt yaratmaq üçün kodun necə görünəcəyi budur createCat():
import learn.javarush.Cat;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {

   public static Cat createCat() throws IOException, IllegalAccessException, InstantiationException, ClassNotFoundException {

       BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
       String className = reader.readLine();

       Class clazz = Class.forName(className);
       Cat cat = (Cat) clazz.newInstance();

       return cat;
   }

public static Object createObject() throws Exception {

   BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
   String className = reader.readLine();

   Class clazz = Class.forName(className);
   Object result = clazz.newInstance();

   return result;
}

   public static void main(String[] args) throws IOException, IllegalAccessException, ClassNotFoundException, InstantiationException {
       System.out.println(createCat());
   }
}
Konsola daxil olun:

learn.javarush.Cat
Konsol çıxışı:

Cat{name='null', age=0}
Bu səhv deyil: qiymətlər nameagekonsolda göstərilir, çünki biz onların çıxışını toString()sinif metodunda proqramlaşdırmışıq Cat. Burada konsoldan obyektini yaradacağımız sinfin adını oxuyuruq. Çalışan proqram obyektini yaradacağı sinfin adını öyrənir. Reflectiondan istifadə nümunələri - 3Qısalıq üçün, nümunənin özündən daha çox yer tutmaması üçün istisnaların düzgün idarə edilməsi üçün kodu buraxdıq. Həqiqi bir proqramda, əlbəttə ki, yanlış adların daxil edildiyi və s. vəziyyətləri idarə etməyə dəyər. Defolt konstruktor kifayət qədər sadə bir şeydir, ona görə də ondan istifadə edərək sinif nümunəsini yaratmaq, gördüyünüz kimi, çətin deyil :) Və metoddan istifadə edərək, newInstance()biz bu sinfin yeni obyektini yaradırıq. Sinif konstruktorunun parametrləri giriş kimi qəbul etməsi başqa məsələdir Cat. Gəlin standart konstruktoru sinifdən çıxaraq və kodumuzu yenidən işə salmağa çalışaq.

null
java.lang.InstantiationException: learn.javarush.Cat
  at java.lang.Class.newInstance(Class.java:427)
Nəsə xəta baş verdi! Defolt konstruktor vasitəsilə obyekt yaratmaq metodunu çağırdığımız üçün xəta aldıq. Amma indi bizim belə dizaynerimiz yoxdur. Bu o deməkdir ki, metod işləyərkən newInstance()əksetmə mexanizmi köhnə konstruktorumuzu iki parametrlə istifadə edəcək:
public Cat(String name, int age) {
   this.name = name;
   this.age = age;
}
Ancaq parametrlərlə heç nə etmədik, sanki onları tamamilə unutmuşuq! Yansıtmadan istifadə edərək onları konstruktora ötürmək üçün onu bir az düzəltməli olacaqsınız:
import learn.javarush.Cat;

import java.lang.reflect.InvocationTargetException;

public class Main {

   public static Cat createCat()  {

       Class clazz = null;
       Cat cat = null;

       try {
           clazz = Class.forName("learn.javarush.Cat");
           Class[] catClassParams = {String.class, int.class};
           cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Barsik", 6);
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       } catch (InstantiationException e) {
           e.printStackTrace();
       } catch (IllegalAccessException e) {
           e.printStackTrace();
       } catch (NoSuchMethodException e) {
           e.printStackTrace();
       } catch (InvocationTargetException e) {
           e.printStackTrace();
       }

       return cat;
   }

   public static void main(String[] args) {
       System.out.println(createCat());
   }
}
Konsol çıxışı:

Cat{name='Barsik', age=6}
Proqramımızda baş verənlərə daha yaxından nəzər salaq. Bir sıra obyektlər yaratdıq Class.
Class[] catClassParams = {String.class, int.class};
Onlar konstruktorumuzun parametrlərinə uyğundur (bizdə sadəcə Stringvə parametrləri var int). Onları metoda ötürürük clazz.getConstructor()və tələb olunan konstruktora giriş əldə edirik. Bundan sonra, yalnız lazımi parametrlərlə metodu çağırmaq qalır newInstance()və obyekti bizə lazım olan sinifə açıq şəkildə köçürməyi unutmayın - Cat.
cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Barsik", 6);
Nəticədə obyektimiz uğurla yaradılacaq! Konsol çıxışı:

Cat{name='Barsik', age=6}
Davam edək :)

Obyekt sahəsinin dəyərini adla necə əldə etmək və təyin etmək olar

Təsəvvür edin ki, başqa bir proqramçı tərəfindən yazılmış bir sinifdən istifadə edirsiniz. Ancaq onu redaktə etmək imkanınız yoxdur. Məsələn, JAR-da paketlənmiş hazır sinif kitabxanası. Siz sinif kodunu oxuya bilərsiniz, lakin onu dəyişə bilməzsiniz. Bu kitabxanada sinfi yaradan proqramçı (qoy bizim köhnə sinifimiz olsun Cat) son dizayndan əvvəl kifayət qədər yuxu almadı və sahə üçün alıcı və təyinediciləri çıxardı age. İndi bu sinif sizə gəldi. Bu, ehtiyaclarınıza tam cavab verir, çünki proqramda sadəcə obyektlərə ehtiyacınız var Cat. Ancaq eyni sahə ilə onlara ehtiyacınız var age! Bu problemdir: biz sahəyə çata bilmirik, çünki onun dəyişdiricisi var privatevə alıcılar və təyinedicilər bu sinfin potensial tərtibatçısı tərəfindən silinib :/ Yaxşı, bu vəziyyətdə də refleksiya bizə kömək edə bilər! Sinif koduna girişimiz Catvar : ən azı onun hansı sahələri olduğunu və onların nə adlandığını öyrənə bilərik. Bu məlumatla silahlanaraq problemimizi həll edirik:
import learn.javarush.Cat;

import java.lang.reflect.Field;

public class Main {

   public static Cat createCat()  {

       Class clazz = null;
       Cat cat = null;
       try {
           clazz = Class.forName("learn.javarush.Cat");
           cat = (Cat) clazz.newInstance();

           //с полем name нам повезло - для него в классе есть setter
           cat.setName("Barsik");

           Field age = clazz.getDeclaredField("age");

           age.setAccessible(true);

           age.set(cat, 6);

       } catch (IllegalAccessException e) {
           e.printStackTrace();
       } catch (InstantiationException e) {
           e.printStackTrace();
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       } catch (NoSuchFieldException e) {
           e.printStackTrace();
       }

       return cat;
   }

   public static void main(String[] args) {
       System.out.println(createCat());
   }
}
Şərhdə deyildiyi kimi, namesahə ilə hər şey sadədir: sinifin tərtibatçıları bunun üçün bir tənzimləyici təqdim etdilər. Siz həmçinin standart konstruktorlardan obyektlərin necə yaradılacağını artıq bilirsiniz: bunun üçün bir üsul var newInstance(). Ancaq ikinci sahə ilə tinker etməli olacaqsınız. Gəlin burada nə baş verdiyini anlayaq :)
Field age = clazz.getDeclaredField("age");
Burada biz obyektimizdən istifadə edərək sahəyə Class clazzdaxil oluruq . Bizə yaş sahəsini obyekt kimi əldə etmək imkanı verir . Lakin bu hələ kifayət deyil, çünki sahələrə sadəcə olaraq dəyərlər təyin edilə bilməz. Bunu etmək üçün, metoddan istifadə edərək sahəni "mövcud" etmək lazımdır : agegetDeclaredField()Field ageprivatesetAccessible()
age.setAccessible(true);
Bunun həyata keçirildiyi sahələrə qiymətlər təyin edilə bilər:
age.set(cat, 6);
Gördüyünüz kimi, bizdə tərsinə çevrilmiş bir növ təyinedicimiz var: biz sahəyə Field ageonun dəyərini təyin edirik, həm də bu sahənin təyin edilməli olduğu obyekti ona ötürürük. Metodumuzu işlədək main()və görək:

Cat{name='Barsik', age=6}
Əla, biz hər şeyi etdik! :) Görək daha hansı imkanlarımız var...

Obyektin metodunu adla necə çağırmaq olar

Əvvəlki nümunədən vəziyyəti bir az dəyişdirək. Deyək ki, sinif tərtibatçısı Catsahələrlə səhv etdi - hər ikisi mövcuddur, onlar üçün alıcılar və tənzimləyicilər var, hər şey qaydasındadır. Problem fərqlidir: o, bizə mütləq ehtiyac duyduğumuz metodu özəl etdi:
private void sayMeow() {

   System.out.println("Meow!");
}
Nəticədə Catproqramımızda obyektlər yaradacağıq, lakin onların metodunu çağıra bilməyəcəyik sayMeow(). Miovlamayan pişiklərimiz olacaqmı? Olduqca qəribə :/ Bunu necə düzəldə bilərəm? Bir daha Reflection API köməyə gəlir! Biz tələb olunan metodun adını bilirik. Qalanı texnika məsələsidir:
import learn.javarush.Cat;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main {

   public static void invokeSayMeowMethod()  {

       Class clazz = null;
       Cat cat = null;
       try {

           cat = new Cat("Barsik", 6);

           clazz = Class.forName(Cat.class.getName());

           Method sayMeow = clazz.getDeclaredMethod("sayMeow");

           sayMeow.setAccessible(true);

           sayMeow.invoke(cat);

       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       } catch (NoSuchMethodException e) {
           e.printStackTrace();
       } catch (IllegalAccessException e) {
           e.printStackTrace();
       } catch (InvocationTargetException e) {
           e.printStackTrace();
       }
   }

   public static void main(String[] args) {
       invokeSayMeowMethod();
   }
}
Burada biz özəl sahəyə giriş vəziyyətində olduğu kimi hərəkət edirik. Əvvəlcə sinif obyektinə daxil edilmiş lazım olan metodu alırıq Method:
Method sayMeow = clazz.getDeclaredMethod("sayMeow");
Köməyi ilə getDeclaredMethod()siz özəl üsullara “çatmaq” olar. Sonra metodu çağırıla bilən hala gətiririk:
sayMeow.setAccessible(true);
Və nəhayət, istədiyiniz obyektdə metodu çağırırıq:
sayMeow.invoke(cat);
Metodun çağırılması həm də “əksinə çağırış” kimi görünür: biz obyekti nöqtədən ( cat.sayMeow()) istifadə edərək tələb olunan metoda yönəltməyə adət etmişik və əks etdirmə ilə işləyərkən onun çağırılması lazım olan obyekti metoda keçirik. . Konsolda nələrimiz var?

Meow!
Hər şey alındı! :) İndi Java-da əks etdirmə mexanizminin bizə nə qədər geniş imkanlar verdiyini görürsünüz. Çətin və gözlənilməz situasiyalarda (qapalı kitabxanadan olan sinif nümunələrində olduğu kimi) bu, həqiqətən də bizə çox kömək edə bilər. Bununla belə, hər bir böyük güc kimi, bu da böyük məsuliyyəti nəzərdə tutur. Refleksiyanın çatışmazlıqları Oracle saytında xüsusi bölmədə yazılıb . Üç əsas çatışmazlıq var:
  1. Məhsuldarlıq azalır. Yansıtma ilə çağırılan metodlar normal çağırılan metodlardan daha aşağı performansa malikdir.

  2. Təhlükəsizlik məhdudiyyətləri var. Yansıtma mexanizmi işləmə zamanı proqramın davranışını dəyişməyə imkan verir. Ancaq real layihədə iş mühitinizdə bunu etməyə imkan verməyən məhdudiyyətlər ola bilər.

  3. Daxili məlumatların açıqlanması riski. Yansıtmadan istifadənin birbaşa inkapsulyasiya prinsipini pozduğunu başa düşmək vacibdir: bu, bizə özəl sahələrə, metodlara və s. Hesab edirəm ki, OOP prinsiplərinin birbaşa və kobud şəkildə pozulmasına yalnız ən ekstremal hallarda, sizdən asılı olmayan səbəblərə görə problemi həll etməyin başqa yolları olmadıqda müraciət edilməli olduğunu izah etməyə ehtiyac yoxdur.

Yansıtma mexanizmini ağıllı şəkildə və yalnız qarşısını almaq mümkün olmayan hallarda istifadə edin və onun çatışmazlıqlarını unutma. Bununla mühazirəmiz yekunlaşır! Olduqca böyük oldu, amma bu gün çox yeni şeylər öyrəndiniz :)
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION