CodeGym /Blog Java /Aleatoriu /Reflection API: Reflection. Partea întunecată a Java
John Squirrels
Nivel
San Francisco

Reflection API: Reflection. Partea întunecată a Java

Publicat în grup
Salutări, tânără Padawan. În acest articol, vă voi spune despre Forță, o putere pe care programatorii Java o folosesc doar în situații aparent imposibile. Partea întunecată a Java este API-ul Reflection. În Java, reflectarea este implementată folosind API-ul Java Reflection.

Ce este reflexia Java?

Există o definiție scurtă, precisă și populară pe Internet. Reflecția ( din latină târziu reflexio - a întoarce înapoi ) este un mecanism de explorare a datelor despre un program în timp ce acesta rulează. Reflection vă permite să explorați informații despre câmpuri, metode și constructori de clasă. Reflection vă permite să lucrați cu tipuri care nu erau prezente în timpul compilării, dar care au devenit disponibile în timpul rulării. Reflecția și un model logic consistent pentru emiterea de informații de eroare fac posibilă crearea unui cod dinamic corect. Cu alte cuvinte, înțelegerea modului în care funcționează reflecția în Java vă va deschide o serie de oportunități uimitoare. Puteți jongla literalmente cu clasele și componentele lor. Iată o listă de bază a ceea ce permite reflecția:
  • Învață/determină clasa unui obiect;
  • Obțineți informații despre modificatorii, câmpurile, metodele, constantele, constructorii și superclasele unei clase;
  • Aflați ce metode aparțin interfețelor implementate;
  • Creați o instanță a unei clase al cărei nume de clasă este necunoscut până la momentul rulării;
  • Obține și setează valori ale câmpurilor unui obiect după nume;
  • Apelați metoda unui obiect după nume.
Reflection este folosit în aproape toate tehnologiile moderne Java. Este greu de imaginat că Java, ca platformă, ar fi putut obține o adoptare atât de răspândită fără reflecție. Cel mai probabil, nu ar fi avut-o. Acum că sunteți familiarizat în general cu reflecția ca concept teoretic, să trecem la aplicarea sa practică! Nu vom învăța toate metodele API-ului Reflection, ci doar pe cele pe care le veți întâlni de fapt în practică. Deoarece reflecția implică lucrul cu clase, vom începe cu o clasă simplă numită 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);
   }
}
După cum puteți vedea, aceasta este o clasă foarte de bază. Constructorul cu parametrii este comentat în mod deliberat. Vom reveni la asta mai târziu. Dacă te-ai uitat cu atenție la conținutul clasei, probabil ai observat absența unui getter pentru câmpul de nume . Câmpul de nume în sine este marcat cu modificatorul de acces privat : nu îl putem accesa în afara clasei în sine, ceea ce înseamnă că nu îi putem prelua valoarea. " Deci care este problema ?" tu spui. „Adăugați un getter sau modificați modificatorul de acces”. Și ai avea dreptate, dacă nuMyClassa fost într-o bibliotecă AAR compilată sau într-un alt modul privat fără posibilitatea de a face modificări. În practică, acest lucru se întâmplă tot timpul. Și un programator nepăsător a uitat pur și simplu să scrie un getter . Acesta este chiar momentul să vă amintiți reflecția! Să încercăm să ajungem la câmpul de nume privat al MyClassclasei:

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
}
Să analizăm ce tocmai s-a întâmplat. În Java, există o clasă minunată numită Class. Reprezintă clase și interfețe într-o aplicație Java executabilă. Nu vom acoperi relația dintre Classși ClassLoader, deoarece nu acesta este subiectul acestui articol. Apoi, pentru a prelua câmpurile acestei clase, trebuie să apelați getFields()metoda. Această metodă va returna toate câmpurile accesibile ale acestei clase. Acest lucru nu funcționează pentru noi, deoarece domeniul nostru este privat , așa că folosim getDeclaredFields()metoda. Această metodă returnează și o serie de câmpuri de clasă, dar acum include câmpuri private și protejate . În acest caz, știm numele câmpului care ne interesează, așa că putem folosi metoda getDeclaredField(String), undeStringeste numele câmpului dorit. Notă: getFields()și getDeclaredFields()nu returnați câmpurile unei clase părinte! Grozav. Avem un Fieldobiect care face referire la numele nostru . Deoarece domeniul nu era public , trebuie să acordăm acces pentru a lucra cu el. Metoda setAccessible(true)ne permite să mergem mai departe. Acum câmpul nume este sub controlul nostru complet! Puteți recupera valoarea sa apelând metoda Fieldobiectului get(Object), unde Objecteste o instanță a MyClassclasei noastre. Convertim tipul Stringși atribuim valoarea variabilei noastre de nume . Dacă nu găsim un setter care să seteze o nouă valoare în câmpul nume, puteți folosi metoda set :

field.set(myClass, (String) "new value");
Felicitări! Tocmai ați stăpânit elementele de bază ale reflecției și ați accesat un domeniu privat ! Acordați atenție blocajului try/catchși tipurilor de excepții gestionate. IDE-ul vă va spune că prezența lor este necesară de la sine, dar puteți spune clar după numele lor de ce sunt aici. Trecând peste! După cum probabil ați observat, MyClassclasa noastră are deja o metodă de afișare a informațiilor despre datele clasei:

private void printData(){
       System.out.println(number + name);
   }
Dar acest programator și-a lăsat amprentele și aici. Metoda are un modificator de acces privat și trebuie să ne scriem propriul cod pentru a afișa date de fiecare dată. Ce mizerie. Unde s-a dus reflecția noastră? Scrieți următoarea funcție:

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();
   }
}
Procedura de aici este aproximativ aceeași cu cea utilizată pentru preluarea unui câmp. Accesăm metoda dorită după nume și acordăm acces la ea. Și pe Methodobiect numim invoke(Object, Args)metoda, unde Objecteste și o instanță a MyClassclasei. Argssunt argumentele metodei, deși al nostru nu are. Acum folosim printDatafuncția pentru a afișa informații:

public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; //?
   printData(myClass); // Output: 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
}
Ura! Acum avem acces la metoda privată a clasei. Dar dacă metoda are argumente și de ce este comentat constructorul? Totul la timpul său. Este clar din definiția de la început că reflectarea vă permite să creați instanțe ale unei clase în timpul rulării (în timp ce programul rulează)! Putem crea un obiect folosind numele complet al clasei. Numele complet al clasei este numele clasei, inclusiv calea pachetului său .
Reflection API: Reflection.  Partea întunecată a Java - 2
În ierarhia pachetului meu , numele complet al MyClass ar fi „reflection.MyClass”. Există, de asemenea, o modalitate simplă de a învăța numele unei clase (returnați numele clasei ca șir):

MyClass.class.getName()
Să folosim reflexia Java pentru a crea o instanță a clasei:

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
}
Când pornește o aplicație Java, nu toate clasele sunt încărcate în JVM. Dacă codul dvs. nu se referă la MyClassclasă, atunci ClassLoader, care este responsabil pentru încărcarea claselor în JVM, nu va încărca niciodată clasa. Asta înseamnă că trebuie să forțați ClassLoadersă o încărcați și să obțineți o descriere a clasei sub forma unei Classvariabile. De aceea avem forName(String)metoda, unde Stringeste numele clasei a cărei descriere avem nevoie. După obținerea obiectului Сlass, apelarea metodei newInstance()va returna un Objectobiect creat folosind acea descriere. Tot ce a mai rămas este să furnizăm acest obiect la noiMyClassclasă. Misto! A fost dificil, dar de înțeles, sper. Acum putem crea o instanță a unei clase într-o singură linie! Din păcate, abordarea descrisă va funcționa numai cu constructorul implicit (fără parametri). Cum apelați metode și constructori cu parametri? Este timpul să anulăm comentariile constructorului nostru. După cum era de așteptat, newInstance()nu poate găsi constructorul implicit și nu mai funcționează. Să rescriem instanțiarea clasei:

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
}
Metoda getConstructors()ar trebui apelată în definiția clasei pentru a obține constructori de clasă și apoi getParameterTypes()ar trebui apelată pentru a obține parametrii unui constructor:

Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
   Class[] paramTypes = constructor.getParameterTypes();
   for (Class paramType : paramTypes) {
       System.out.print(paramType.getName() + " ");
   }
   System.out.println();
}
Asta ne aduce pe toți constructorii și parametrii lor. În exemplul meu, mă refer la un constructor specific cu parametri specifici cunoscuți anterior. Și pentru a apela acest constructor, folosim newInstancemetoda, căreia îi trecem valorile acestor parametri. Același lucru va fi și atunci când folosiți invokemetode de apelare. Acest lucru ridică întrebarea: când este utilă apelarea constructorilor prin reflecție? După cum am menționat deja la început, tehnologiile Java moderne nu se pot descurca fără API-ul Java Reflection. De exemplu, Dependency Injection (DI), care combină adnotările cu reflectarea metodelor și constructorilor pentru a forma popularul Darerbibliotecă pentru dezvoltarea Android. După ce ați citit acest articol, vă puteți considera cu încredere educat în modalitățile API-ului Java Reflection. Ei nu numesc reflexia partea întunecată a Java degeaba. Încalcă complet paradigma OOP. În Java, încapsularea ascunde și restricționează accesul altora la anumite componente ale programului. Când folosim modificatorul privat, intenționăm ca acel câmp să fie accesat numai din clasa în care există. Și construim arhitectura ulterioară a programului pe baza acestui principiu. În acest articol, am văzut cum puteți folosi reflecția pentru a vă forța drumul oriunde. Modelul de design creațional Singletoneste un bun exemplu în acest sens ca soluție arhitecturală. Ideea de bază este că o clasă care implementează acest model va avea o singură instanță în timpul execuției întregului program. Acest lucru se realizează prin adăugarea modificatorului de acces privat la constructorul implicit. Și ar fi foarte rău dacă un programator ar folosi reflecția pentru a crea mai multe instanțe ale unor astfel de clase. Apropo, am auzit recent un coleg punând o întrebare foarte interesantă: poate fi moștenită o clasă care implementează modelul Singleton? S-ar putea ca, în acest caz, chiar și reflecția ar fi neputincioasă? Lasă feedback-ul tău despre articol și răspunsul tău în comentariile de mai jos și pune-ți propriile întrebări acolo!

Mai multe lecturi:

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