JavaRush /Java Blogu /Random-AZ /Reflection API. Refleksiya. Javanın qaranlıq tərəfi
Oleksandr Klymenko
Səviyyə
Харків

Reflection API. Refleksiya. Javanın qaranlıq tərəfi

Qrupda dərc edilmişdir
Salam, gənc Padavan. Bu yazıda mən sizə gücdən java proqramçılarının ancaq ümidsiz görünən vəziyyətdə istifadə etdiyi Qüvvət haqqında məlumat verəcəyəm. Beləliklə, Java-nın qaranlıq tərəfi -Reflection API
Reflection API.  Refleksiya.  Java-nın qaranlıq tərəfi - 1
Java-da əks etdirmə Java Reflection API-dən istifadə etməklə həyata keçirilir. Bu əks nədir? İnternetdə də məşhur olan qısa və dəqiq tərif var. Refleksiya (Latınca gec refleksio - geriyə qayıtmaq) proqramın icrası zamanı onun haqqında məlumatların öyrənilməsi mexanizmidir. Reflection sizə sahələr, metodlar və sinif konstruktorları haqqında məlumatı yoxlamağa imkan verir. Yansıtma mexanizminin özü kompilyasiya zamanı çatışmayan, lakin proqramın icrası zamanı görünən növləri emal etməyə imkan verir. Yansıtma və səhvlərin bildirilməsi üçün məntiqi ardıcıl modelin olması düzgün dinamik kodu yaratmağa imkan verir. Başqa sözlə, əksin Java-da necə işlədiyini başa düşmək sizin üçün bir sıra heyrətamiz imkanlar açır. Siz sanki dərsləri və onların komponentlərini hoqqa çıxara bilərsiniz.
Reflection API.  Refleksiya.  Java-nın qaranlıq tərəfi - 2
Burada əks etdirməyə imkan verənlərin əsas siyahısı verilmişdir:
  • Obyektin sinfini tapmaq/müəyyən etmək;
  • Sinif dəyişdiriciləri, sahələr, metodlar, sabitlər, konstruktorlar və supersiniflər haqqında məlumat əldə etmək;
  • Hansı metodların həyata keçirilən interfeysə/interfeyslərə aid olduğunu öyrənin;
  • Sinfin nümunəsini yaradın və proqram icra olunana qədər sinfin adı məlum deyil;
  • Obyekt sahəsinin dəyərini adına görə alın və təyin edin;
  • Obyektin metodunu adla çağırın.
Reflection demək olar ki, bütün müasir Java texnologiyalarında istifadə olunur. Təsəvvür etmək çətindir ki, Java bir platforma olaraq düşünmədən belə böyük qəbula nail ola bilərdi. Çox güman ki, bacarmadım. Refleksiyanın ümumi nəzəri ideyası ilə tanış oldunuz, indi onun praktik tətbiqinə keçək! Biz Reflection API-nin bütün üsullarını öyrənməyəcəyik, yalnız praktikada rast gəlinənləri öyrənəcəyik. Yansıtma mexanizmi siniflərlə işləməyi nəzərdə tutduğundan, sadə bir sinifimiz olacaq - 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);
   }
}
Gördüyümüz kimi, bu ən çox yayılmış sinifdir. Parametrləri olan konstruktor bir səbəbə görə şərh olunur, buna daha sonra qayıdacağıq. Sinfin məzmununa diqqətlə baxsanız, yəqin ki, getter'a for the ' ın olmadığını gördünüz name. Sahənin özü namegiriş dəyişdiricisi ilə işarələnmişdir private; biz ona sinfin özündən kənarda daxil ola bilməyəcəyik; =>onun dəyərini ala bilmərik. "Bəs problem nədir? - deyirsen. " getterGiriş dəyişdiricisini əlavə edin və ya dəyişdirin." Və siz haqlı olacaqsınız, amma MyClasso, tərtib edilmiş aar kitabxanasında və ya redaktə girişi olmayan başqa bir qapalı moduldadırsa və praktikada bu çox tez-tez baş verir. Və bəzi diqqətsiz proqramçı sadəcə yazmağı unudub getter. Düşüncə haqqında xatırlamağın vaxtı gəldi! Gəlin sinif privatesahəsinə girməyə çalışaq : 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
}
İndi burada nə baş verdiyini anlayaq. Javada gözəl bir sinif var Class. O, icra edilə bilən Java proqramında sinifləri və interfeysləri təmsil edir. Classvə arasında əlaqəyə toxunmayacağıq ClassLoader. bu məqalənin mövzusu deyil. Bundan sonra, bu sinfin sahələrini əldə etmək üçün metodu çağırmalısınız getFields(), bu üsul bizə sinfin bütün mövcud sahələrini qaytaracaq. Sahəmiz olduğu üçün bu bizim üçün uyğun deyil, privateona görə də metoddan istifadə edirik.Bu getDeclaredFields()üsul həm də sinif sahələrinin massivini qaytarır, lakin indi həm privatevə həm də protected. Vəziyyətimizdə bizi maraqlandıran sahənin adını bilirik və metoddan istifadə edə bilərik getDeclaredField(String), Stringistədiyiniz sahənin adı haradadır. Qeyd: getFields()getDeclaredFields()valideyn sinifinin sahələrini geri qaytarmayın! Field Əla, biz bizim linki olan obyekt aldıq name. Çünki sahə (ictimai) deyildi публичным, onunla işləmək üçün giriş təmin edilməlidir. Metod setAccessible(true)işləməyə davam etməyə imkan verir. İndi sahə nametamamilə bizim nəzarətimizdədir! get(Object)Obyektə zəng etməklə onun dəyərini əldə edə bilərsiniz Field, burada Objectbizim sinifin nümunəsi var MyClass. Biz onu yayırıq Stringvə dəyişənimizə təyin edirik name. Birdən bizdə 'a yoxdursa setter, ad sahəsi üçün yeni dəyər təyin etmək üçün metoddan istifadə edə bilərik set:
field.set(myClass, (String) "new value");
Təbrik edirik! Siz indicə əsas əksetmə mexanizmini mənimsədiniz və sahəyə daxil ola bildiniz private! try/catchİstisnaların blokuna və növlərinə diqqət yetirin . IDE-nin özü onların məcburi mövcudluğunu göstərəcək, lakin adları onların niyə burada olduqlarını aydınlaşdırır. Davam et! Diqqət etdiyiniz kimi, bizim MyClassartıq sinif məlumatları haqqında məlumatı göstərmək üçün bir üsul var:
private void printData(){
       System.out.println(number + name);
   }
Amma bu proqramçı burada da miras qoyub. Metod giriş modifikatorunun altındadır privatevə hər dəfə çıxış kodunu özümüz yazmalı olduq. Düzgün deyil, əksimiz haradadır?... Aşağıdakı funksiyanı yazaq:
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();
   }
}
Burada prosedur təxminən bir sahənin alınması ilə eynidir - adla istədiyiniz metodu alırıq və ona giriş veririk. MethodVə istifadə etdiyimiz obyekti çağırmaq üçün invoke(Оbject, Args)burada Оbjectda sinif nümunəsidir MyClass. Args- metod arqumentləri - bizimkilərin heç biri yoxdur. İndi məlumatları göstərmək üçün funksiyadan istifadə edirik 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
}
Hurray, indi sinfin özəl metoduna çıxışımız var. Bəs metodun hələ də arqumentləri varsa və nə üçün şərh edilmiş konstruktor var? Hər şeyin öz vaxtı var. Başlanğıcdakı tərifdən aydın olur ki, əks etdirmə bir rejimdə runtime(proqram işləyərkən) sinfin nümunələrini yaratmağa imkan verir! Biz həmin sinfin tam adı ilə bir sinfin obyektini yarada bilərik. Tam uyğunlaşdırılmış sinif adı sinifin adıdır, ona gedən yol verilir package.
Reflection API.  Refleksiya.  Java-nın qaranlıq tərəfi - 3
Mənim iyerarxiyamda packagetam ad MyClass“ ” olacaq reflection.MyClass. Siz həmçinin sinfin adını sadə şəkildə öyrənə bilərsiniz (sinf adını sətir kimi qaytaracaq):
MyClass.class.getName()
Refiksasiyadan istifadə edərək sinif nümunəsini yaradaq:
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 tətbiqi işə başlayanda bütün siniflər JVM-ə yüklənmir. Əgər kodunuz sinifə aid deyilsə MyClass, o zaman JVM-ə dərslərin yüklənməsinə cavabdeh olan şəxs, yəni ClassLoader, onu heç vaxt orada yükləməz. Buna görə də, onu yükləməyə məcbur etməliyik ClassLoadervə tipli bir dəyişən şəklində sinifimizin təsvirini almalıyıq Class. Bu tapşırıq üçün bir üsul var forName(String), burada Stringtəsvirini tələb etdiyimiz sinifin adı var. Qəbul edildikdən sonra eyni təsvirə uyğun olaraq yaradılacaq Сlassmetod çağırışı newInstance()qayıdacaq . ObjectBu obyekti sinfimizə gətirmək qalır MyClass. Əla! Çətin idi, amma ümid edirəm ki, başa düşüləndir. İndi biz bir sətirdən hərfi mənada bir sinif nümunəsi yarada bilərik! Təəssüf ki, təsvir edilən üsul yalnız standart konstruktorla işləyəcək (parametrlər olmadan). Arqumentlərlə metodları və parametrlərlə konstruktorları necə çağırmaq olar? Konstruktorumuzu şərhdən çıxarmağın vaxtı gəldi. Gözlənildiyi kimi, newInstance()o, standart konstruktoru tapmır və artıq işləmir. Sinif nümunəsinin yaradılmasını yenidən yazaq:
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
}
Sinif konstruktorlarını əldə etmək üçün sinif təsvirindən metodu çağırın getConstructors()və konstruktor parametrlərini əldə etmək üçün zəng edin 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();
}
Bu yolla biz bütün konstruktorları və bütün parametrləri onlara çatdırırıq. Mənim nümunəmdə spesifik, artıq məlum parametrləri olan xüsusi konstruktora çağırış var. newInstanceVə bu konstruktoru çağırmaq üçün bu parametrlər üçün dəyərləri təyin etdiyimiz metoddan istifadə edirik . invokeEyni şey zəng üsulları üçün də baş verəcəkdir . Sual yaranır: konstruktorların əks etdirici çağırışı harada faydalı ola bilər? Müasir java texnologiyaları, əvvəldə qeyd edildiyi kimi, Reflection API olmadan edə bilməz. Məsələn, DI (Dependency Injection), burada annotasiyalar metodların və konstruktorların əksi ilə birləşərək Android inkişafında məşhur olan Dagger kitabxanasını təşkil edir. Bu məqaləni oxuduqdan sonra özünüzü Reflection API mexanizmlərində inamla maariflənmiş hesab edə bilərsiniz. Əbəs yerə əksi Java-nın qaranlıq tərəfi adlandırmır. OOP paradiqmasını tamamilə pozur. Java-da inkapsulyasiya bəzi proqram komponentlərinin digərlərinə çıxışını gizlətməyə və məhdudlaşdırmağa xidmət edir. Şəxsi dəyişdiricidən istifadə etməklə, bu sahəyə girişin yalnız bu sahənin mövcud olduğu sinif daxilində olacağını nəzərdə tuturuq, bunun əsasında proqramın sonrakı arxitekturasını qururuq. Bu yazıda hər hansı bir yerə çatmaq üçün əksetmədən necə istifadə edə biləcəyinizi gördük. Memarlıq həlli şəklində yaxşı bir nümunə generativ dizayn nümunəsidir - Singleton. Onun əsas ideyası ondan ibarətdir ki, proqramın bütün fəaliyyəti ərzində bu şablonu həyata keçirən sinfin yalnız bir nüsxəsi olmalıdır. Bu, standart giriş dəyişdiricisini konstruktor üçün özəl olaraq təyin etməklə həyata keçirilir. Öz əksi olan hansısa proqramçı belə siniflər yaratsa, çox pis olar. Yeri gəlmişkən, bu yaxınlarda əməkdaşımdan eşitdiyim çox maraqlı bir sual var: şablonu həyata keçirən sinfin Singletonvarisləri ola bilərmi? Ola bilərmi ki, bu halda hətta düşüncə də gücsüzdür? Məqalə və cavabla bağlı rəyinizi şərhlərdə yazın, həmçinin suallarınızı verin! Reflection API-nin əsl gücü, yəqin ki, Java-nın qaranlıq tərəfi haqqında gələcək məqalədə danışacağımız Runtime Annotations ilə birlikdə gəlir. Diqqətinizə görə təşəkkürlər!
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION