CodeGym /Java blog /Tilfældig /Refleksion API: Reflektion. Den mørke side af Java
John Squirrels
Niveau
San Francisco

Refleksion API: Reflektion. Den mørke side af Java

Udgivet i gruppen
Hilsen unge Padawan. I denne artikel vil jeg fortælle dig om Force, en kraft, som Java-programmører kun bruger i tilsyneladende umulige situationer. Den mørke side af Java er Reflection API. I Java implementeres refleksion ved hjælp af Java Reflection API.

Hvad er Java-reflektion?

Der er en kort, præcis og populær definition på internettet. Refleksion ( fra sent latin reflexio - at vende tilbage ) er en mekanisme til at udforske data om et program, mens det kører. Reflektion lader dig udforske information om felter, metoder og klassekonstruktører. Reflektion lader dig arbejde med typer, der ikke var til stede på kompileringstidspunktet, men som blev tilgængelige under kørselstiden. Refleksion og en logisk konsistent model for udstedelse af fejlinformation gør det muligt at skabe korrekt dynamisk kode. Med andre ord vil en forståelse af, hvordan refleksion fungerer i Java, åbne op for en række fantastiske muligheder for dig. Du kan bogstaveligt talt jonglere med klasser og deres komponenter. Her er en grundlæggende liste over, hvad refleksion tillader:
  • Lær/bestem et objekts klasse;
  • Få information om en klasses modifikatorer, felter, metoder, konstanter, konstruktører og superklasser;
  • Find ud af hvilke metoder der hører til implementerede grænseflader;
  • Opret en forekomst af en klasse, hvis klassenavn er ukendt indtil køretid;
  • Hent og indstil værdier for et objekts felter efter navn;
  • Kald et objekts metode ved navn.
Refleksion bruges i næsten alle moderne Java-teknologier. Det er svært at forestille sig, at Java som platform kunne have opnået en så udbredt adoption uden refleksion. Det ville den højst sandsynligt ikke have. Nu hvor du generelt er bekendt med refleksion som et teoretisk begreb, lad os gå videre til dens praktiske anvendelse! Vi lærer ikke alle Reflection API's metoder – kun dem, du rent faktisk vil støde på i praksis. Da refleksion involverer arbejde med klasser, starter vi med en simpel klasse kaldet 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 meget grundlæggende klasse. Konstruktøren med parametre er bevidst kommenteret ud. Det vender vi tilbage til senere. Hvis du kiggede grundigt på indholdet af klassen, har du sikkert bemærket fraværet af en getter til navnefeltet . Selve navnefeltet er markeret med den private adgangsmodifikator: vi kan ikke få adgang til det uden for selve klassen, hvilket betyder, at vi ikke kan hente dens værdi . " Så hvad er problemet ?" du siger. "Tilføj en getter , eller skift adgangsmodifikatoren". Og du ville have ret, medmindreMyClassvar i et kompileret AAR-bibliotek eller i et andet privat modul uden mulighed for at foretage ændringer. I praksis sker det hele tiden. Og en eller anden skødesløs programmør glemte simpelthen at skrive en getter . Dette er netop tiden til at huske refleksion! Lad os prøve at 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
}
Lad os analysere, hvad der lige skete. I Java er der en vidunderlig klasse kaldet Class. Det repræsenterer klasser og grænseflader i en eksekverbar Java-applikation. Vi vil ikke dække forholdet mellem Classog ClassLoader, da det ikke er emnet for denne artikel. Dernæst skal du kalde metoden for at hente denne klasses felter getFields(). Denne metode returnerer alle denne klasses tilgængelige felter. Dette virker ikke for os, fordi vores felt er privat , så vi bruger getDeclaredFields()metoden. Denne metode returnerer også en række klassefelter, men nu inkluderer den private og beskyttede felter. I dette tilfælde kender vi navnet på det felt, vi er interesseret i, så vi kan bruge metoden, getDeclaredField(String)hvorStringer det ønskede felts navn. Bemærk: getFields()og getDeclaredFields()returner ikke felterne i en forældreklasse! Store. Vi har et Fieldobjekt, der refererer til vores navn . Da feltet ikke var offentligt , skal vi give adgang til at arbejde med det. Metoden setAccessible(true)lader os komme videre. Nu er navnefeltet under vores fulde kontrol! Du kan hente dens værdi ved at kalde Fieldobjektets get(Object)metode, hvor Objecter en forekomst af vores MyClassklasse. Vi konverterer typen til Stringog tildeler værdien til vores navnevariabel . Hvis vi ikke kan finde en indstiller til at indstille en ny værdi til navnefeltet, kan du bruge sætmetoden :

field.set(myClass, (String) "new value");
Tillykke! Du har lige mestret det grundlæggende i refleksion og tilgået et privat felt! Vær opmærksom på try/catchblokeringen og de typer af undtagelser, der håndteres. IDE vil fortælle dig, at deres tilstedeværelse er påkrævet alene, men du kan tydeligt se ved deres navne, hvorfor de er her. Komme videre! Som du måske har bemærket, MyClasshar vores klasse allerede en metode til at vise information om klassedata:

private void printData(){
       System.out.println(number + name);
   }
Men denne programmør efterlod også sine fingeraftryk her. Metoden har en privat adgangsmodifikator, og vi skal skrive vores egen kode for at vise data hver gang. Sikke et rod. Hvor blev vores refleksion af? Skriv følgende funktion:

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åden her er omtrent den samme som den, der bruges til at hente et felt. Vi tilgår den ønskede metode ved navn og giver adgang til den. Og på objektet Methodkalder vi invoke(Object, Args)metoden, hvor Objecter også en instans af klassen MyClass. Argser metodens argumenter, selvom vores ikke har nogen. Nu bruger vi printDatafunktionen til at vise information:

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! Nu har vi adgang til klassens private metode. Men hvad hvis metoden har argumenter, og hvorfor bliver konstruktøren kommenteret ud? Alt til sin egen tid. Det fremgår tydeligt af definitionen i begyndelsen, at refleksion lader dig oprette forekomster af en klasse under kørsel (mens programmet kører)! Vi kan oprette et objekt ved hjælp af klassens fulde navn. Klassens fulde navn er klassenavnet, inklusive stien til dens pakke .
Reflection API: Reflection.  Den mørke side af Java - 2
I mit pakkehierarki ville det fulde navn på MyClass være "reflection.MyClass". Der er også en enkel måde at lære en klasses navn på (returnér klassens navn som en streng):

MyClass.class.getName()
Lad os bruge Java-reflektion til at oprette en forekomst af 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-applikation starter, er ikke alle klasser indlæst i JVM. Hvis din kode ikke refererer til MyClassklassen, vil ClassLoader, som er ansvarlig for at indlæse klasser i JVM, aldrig indlæse klassen. Det betyder, at du skal tvinge ClassLoadertil at indlæse den og få en klassebeskrivelse i form af en Classvariabel. Det er derfor, vi har forName(String)metoden, hvor Stringer navnet på den klasse, hvis beskrivelse vi har brug for. Efter at have hentet Сlassobjektet, vil kald af metoden newInstance()returnere et Objectobjekt oprettet ved hjælp af denne beskrivelse. Det eneste der er tilbage er at levere denne genstand til voresMyClassklasse. Fedt nok! Det var svært, men forståeligt, håber jeg. Nu kan vi oprette en forekomst af en klasse på bogstaveligt talt én linje! Desværre vil den beskrevne tilgang kun fungere med standardkonstruktøren (uden parametre). Hvordan kalder man metoder og konstruktører med parametre? Det er tid til at fjerne kommentarer fra vores konstruktør. Kan som forventet newInstance()ikke finde standardkonstruktøren og virker ikke længere. Lad os omskrive klasseforekomsten:

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 kaldes på klassedefinitionen for at opnå klassekonstruktører og getParameterTypes()skal derefter kaldes for at få en konstruktørs parametre:

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 os alle konstruktørerne og deres parametre. I mit eksempel refererer jeg til en specifik konstruktør med specifikke, tidligere kendte parametre. Og for at kalde denne konstruktør bruger vi newInstancemetoden, som vi sender værdierne af disse parametre til. Det vil være det samme, når du bruger invoketil at kalde metoder. Dette rejser spørgsmålet: hvornår er det praktisk at kalde konstruktører gennem refleksion? Som allerede nævnt i begyndelsen, kan moderne Java-teknologier ikke klare sig uden Java Reflection API. For eksempel Dependency Injection (DI), som kombinerer annoteringer med refleksion af metoder og konstruktører for at danne den populære Darerbibliotek til Android-udvikling. Efter at have læst denne artikel, kan du trygt betragte dig selv som uddannet i Java Reflection API'ets måder. De kalder ikke refleksion for den mørke side af Java for ingenting. Det bryder fuldstændig OOP-paradigmet. I Java skjuler og begrænser indkapsling andres adgang til visse programkomponenter. Når vi bruger den private modifikator, har vi til hensigt, at feltet kun skal tilgås fra den klasse, hvor det findes. Og vi bygger programmets efterfølgende arkitektur ud fra dette princip. I denne artikel har vi set, hvordan du kan bruge refleksion til at tvinge dig vej overalt. Det kreative designmønster Singletoner et godt eksempel på dette som en arkitektonisk løsning. Den grundlæggende idé er, at en klasse, der implementerer dette mønster, kun vil have én instans under udførelse af hele programmet. Dette opnås ved at tilføje den private adgangsmodifikator til standardkonstruktøren. Og det ville være meget dårligt, hvis en programmør brugte refleksion til at skabe flere forekomster af sådanne klasser. Forresten hørte jeg for nylig en kollega stille et meget interessant spørgsmål: kan en klasse, der implementerer Singleton-mønsteret, nedarves? Kunne det være, at selv refleksion i dette tilfælde ville være magtesløs? Giv din feedback om artiklen og dit svar i kommentarerne nedenfor, og stil dine egne spørgsmål der!

Mere læsning:

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