CodeGym /Java-blogg /Tilfeldig /Refleksjon API: Refleksjon. Den mørke siden av Java
John Squirrels
Nivå
San Francisco

Refleksjon API: Refleksjon. Den mørke siden av Java

Publisert i gruppen
Hilsen, unge Padawan. I denne artikkelen skal jeg fortelle deg om Force, en kraft som Java-programmerere bare bruker i tilsynelatende umulige situasjoner. Den mørke siden av Java er Reflection API. I Java implementeres refleksjon ved hjelp av Java Reflection API.

Hva er Java-refleksjon?

Det er en kort, nøyaktig og populær definisjon på Internett. Refleksjon ( fra senlatinsk reflexio - å vende tilbake ) er en mekanisme for å utforske data om et program mens det kjører. Refleksjon lar deg utforske informasjon om felt, metoder og klassekonstruktører. Refleksjon lar deg jobbe med typer som ikke var til stede på kompileringstidspunktet, men som ble tilgjengelige under kjøretiden. Refleksjon og en logisk konsistent modell for utstedelse av feilinformasjon gjør det mulig å lage korrekt dynamisk kode. Med andre ord, en forståelse av hvordan refleksjon fungerer i Java vil åpne opp for en rekke fantastiske muligheter for deg. Du kan bokstavelig talt sjonglere klasser og deres komponenter. Her er en grunnleggende liste over hva refleksjon tillater:
  • Lære/bestemme et objekts klasse;
  • Få informasjon om en klasses modifikatorer, felt, metoder, konstanter, konstruktører og superklasser;
  • Finn ut hvilke metoder som tilhører implementerte grensesnitt(er);
  • Opprett en forekomst av en klasse hvis klassenavn er ukjent før kjøretid;
  • Hent og angi verdier for et objekts felt etter navn;
  • Kalle et objekts metode ved navn.
Refleksjon brukes i nesten alle moderne Java-teknologier. Det er vanskelig å forestille seg at Java, som plattform, kunne ha oppnådd så utbredt bruk uten refleksjon. Mest sannsynlig ville det ikke gjort det. Nå som du generelt er kjent med refleksjon som et teoretisk konsept, la oss gå videre til den praktiske anvendelsen! Vi vil ikke lære alle metodene til Reflection API – bare de du faktisk vil møte i praksis. Siden refleksjon innebærer arbeid med klasser, starter vi med en enkel klasse kalt 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);
   }
}
Som du kan se, er dette en veldig grunnleggende klasse. Konstruktøren med parametere er bevisst kommentert. Vi kommer tilbake til det senere. Hvis du så nøye på innholdet i klassen, la du sannsynligvis merke til fraværet av en getter for navnefeltet . Selve navnefeltet er merket med modifikatoren for privat tilgang: vi har ikke tilgang til det utenfor selve klassen, noe som betyr at vi ikke kan hente verdien . " Så hva er problemet ?" du sier. "Legg til en getter eller endre tilgangsmodifikatoren". Og du ville ha rett, med mindreMyClassvar i et kompilert AAR-bibliotek eller i en annen privat modul uten mulighet til å gjøre endringer. I praksis skjer dette hele tiden. Og en uforsiktig programmerer glemte rett og slett å skrive en getter . Dette er tiden for å huske refleksjon! La oss prøve å komme til klassens private navnefelt 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
}
La oss analysere hva som nettopp skjedde. I Java er det en fantastisk klasse kalt Class. Den representerer klasser og grensesnitt i en kjørbar Java-applikasjon. Vi vil ikke dekke forholdet mellom Classog ClassLoader, siden det ikke er temaet for denne artikkelen. Deretter, for å hente denne klassens felt, må du kalle getFields()metoden. Denne metoden vil returnere alle denne klassens tilgjengelige felt. Dette fungerer ikke for oss, fordi feltet vårt er privat , så vi bruker getDeclaredFields()metoden. Denne metoden returnerer også en rekke klassefelt, men nå inkluderer den private og beskyttede felt. I dette tilfellet vet vi navnet på feltet vi er interessert i, så vi kan bruke metoden, getDeclaredField(String)hvorStringer det ønskede feltets navn. Merk: getFields()og getDeclaredFields()ikke returner feltene til en foreldreklasse! Flott. Vi har et Fieldobjekt som refererer til navnet vårt . Siden feltet ikke var offentlig , må vi gi tilgang til å jobbe med det. Metoden setAccessible(true)lar oss gå videre. Nå er navnefeltet under full kontroll! Du kan hente verdien ved å kalle Fieldobjektets get(Object)metode, hvor Objecter en forekomst av MyClassklassen vår. Vi konverterer typen til Stringog tildeler verdien til navnevariabelen vår . Hvis vi ikke finner en setter for å sette en ny verdi til navnefeltet, kan du bruke settmetoden :

field.set(myClass, (String) "new value");
Gratulerer! Du har nettopp mestret det grunnleggende om refleksjon og fått tilgang til et privat felt! Vær oppmerksom på blokkeringen try/catchog hvilke typer unntak som håndteres. IDE vil fortelle deg at deres tilstedeværelse er nødvendig alene, men du kan tydelig fortelle ved navnene deres hvorfor de er her. Går videre! Som du kanskje har lagt merke til, MyClasshar klassen vår allerede en metode for å vise informasjon om klassedata:

private void printData(){
       System.out.println(number + name);
   }
Men denne programmereren la sine fingeravtrykk her også. Metoden har en privat tilgangsmodifikator, og vi må skrive vår egen kode for å vise data hver gang. For et rot. Hvor ble det av refleksjonen vår? Skriv følgende funksjon:

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();
   }
}
Fremgangsmåten her er omtrent den samme som den som brukes for å hente et felt. Vi får tilgang til ønsket metode ved navn og gir tilgang til den. Og på objektet Methodkaller vi invoke(Object, Args)metoden, hvor Objecter også en forekomst av MyClassklassen. Argser metodens argumenter, selv om våre ikke har noen. Nå bruker vi printDatafunksjonen til å vise informasjon:

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
}
Hurra! Nå har vi tilgang til klassens private metode. Men hva om metoden har argumenter, og hvorfor blir konstruktøren kommentert? Alt til sin rett tid. Det er klart fra definisjonen i begynnelsen at refleksjon lar deg lage forekomster av en klasse på kjøretid (mens programmet kjører)! Vi kan lage et objekt ved å bruke klassens fulle navn. Klassens fulle navn er klassenavnet, inkludert banen til pakken .
Refleksjon API: Refleksjon.  Den mørke siden av Java - 2
I mitt pakkehierarki vil det fulle navnet til MyClass være "reflection.MyClass". Det er også en enkel måte å lære klassens navn på (retur klassens navn som en streng):

MyClass.class.getName()
La oss bruke Java-refleksjon for å lage en forekomst av klassen:

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
}
Når en Java-applikasjon starter, er ikke alle klasser lastet inn i JVM. Hvis koden din ikke refererer til MyClassklassen, ClassLoadervil , som er ansvarlig for å laste klasser inn i JVM, aldri laste klassen. Det betyr at du må tvinge ClassLoadertil å laste den og få en klassebeskrivelse i form av en Classvariabel. Dette er grunnen til at vi har forName(String)metoden, hvor Stringer navnet på klassen hvis beskrivelse vi trenger. Etter å ha hentet Сlassobjektet, vil oppkalling av metoden newInstance()returnere et Objectobjekt opprettet med den beskrivelsen. Alt som gjenstår er å levere denne gjenstanden til vårMyClassklasse. Kul! Det var vanskelig, men forståelig, håper jeg. Nå kan vi lage en forekomst av en klasse på bokstavelig talt én linje! Dessverre vil den beskrevne tilnærmingen bare fungere med standardkonstruktøren (uten parametere). Hvordan kaller du metoder og konstruktører med parametere? Det er på tide å fjerne kommentarer fra konstruktøren vår. Kan som forventet newInstance()ikke finne standardkonstruktøren og fungerer ikke lenger. La oss skrive om klassens instansiasjon:

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
}
Metoden getConstructors()skal kalles på klassedefinisjonen for å få klassekonstruktører, og getParameterTypes()bør deretter kalles for å få en konstruktørs parametere:

Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
   Class[] paramTypes = constructor.getParameterTypes();
   for (Class paramType : paramTypes) {
       System.out.print(paramType.getName() + " ");
   }
   System.out.println();
}
Det får oss alle konstruktørene og deres parametere. I mitt eksempel refererer jeg til en spesifikk konstruktør med spesifikke, tidligere kjente parametere. Og for å kalle denne konstruktøren bruker vi newInstancemetoden som vi sender verdiene til disse parameterne til. Det vil være det samme når du bruker invoketo call-metoder. Dette reiser spørsmålet: når er det nyttig å kalle konstruktører gjennom refleksjon? Som allerede nevnt i begynnelsen, kan moderne Java-teknologier ikke klare seg uten Java Reflection API. For eksempel Dependency Injection (DI), som kombinerer merknader med refleksjon av metoder og konstruktører for å danne den populære Darerbibliotek for Android-utvikling. Etter å ha lest denne artikkelen, kan du trygt vurdere deg selv som utdannet i Java Reflection API. De kaller ikke refleksjon den mørke siden av Java for ingenting. Det bryter OOP-paradigmet fullstendig. I Java skjuler og begrenser innkapsling andres tilgang til visse programkomponenter. Når vi bruker den private modifikatoren, har vi til hensikt at feltet kun skal åpnes fra klassen der det eksisterer. Og vi bygger programmets påfølgende arkitektur basert på dette prinsippet. I denne artikkelen har vi sett hvordan du kan bruke refleksjon for å tvinge deg frem hvor som helst. Det kreative designmønsteret Singletoner et godt eksempel på dette som en arkitektonisk løsning. Den grunnleggende ideen er at en klasse som implementerer dette mønsteret bare vil ha én forekomst under utførelse av hele programmet. Dette oppnås ved å legge til modifikatoren for privat tilgang til standardkonstruktøren. Og det ville være veldig dårlig om en programmerer brukte refleksjon til å lage flere forekomster av slike klasser. Forresten, jeg hørte nylig en kollega stille et veldig interessant spørsmål: kan en klasse som implementerer Singleton-mønsteret arves? Kan det være at selv refleksjon i dette tilfellet ville være maktesløs? Gi tilbakemelding om artikkelen og svaret ditt i kommentarene nedenfor, og still dine egne spørsmål der!
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION