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.
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):
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.
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 mindreMyClass
var 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 Class
og 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)
hvorString
er det ønskede feltets navn. Merk: getFields()
og getDeclaredFields()
ikke returner feltene til en foreldreklasse! Flott. Vi har et Field
objekt 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 Field
objektets get(Object)
metode, hvor Object
er en forekomst av MyClass
klassen vår. Vi konverterer typen til String
og 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/catch
og 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, MyClass
har 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 Method
kaller vi invoke(Object, Args)
metoden, hvor Object
er også en forekomst av MyClass
klassen. Args
er metodens argumenter, selv om våre ikke har noen. Nå bruker vi printData
funksjonen 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 .

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 MyClass
klassen, ClassLoader
vil , som er ansvarlig for å laste klasser inn i JVM, aldri laste klassen. Det betyr at du må tvinge ClassLoader
til å laste den og få en klassebeskrivelse i form av en Class
variabel. Dette er grunnen til at vi har forName(String)
metoden, hvor String
er navnet på klassen hvis beskrivelse vi trenger. Etter å ha hentet Сlass
objektet, vil oppkalling av metoden newInstance()
returnere et Object
objekt opprettet med den beskrivelsen. Alt som gjenstår er å levere denne gjenstanden til vårMyClass
klasse. 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 newInstance
metoden som vi sender verdiene til disse parameterne til. Det vil være det samme når du bruker invoke
to 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!
Mer lesing: |
---|
GO TO FULL VERSION