
Cat
klasse:
package learn.codegym;
public class Cat {
private String name;
private int age;
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
public void sayMeow() {
System.out.println("Meow!");
}
public void jump() {
System.out.println("Jump!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Du vet alt om det, og du kan se feltene og metodene som det har. Tenk deg at du plutselig trenger å introdusere andre dyreklasser til programmet. Du kan sannsynligvis opprette en klassearvstruktur med en Animal
overordnet klasse for enkelhets skyld. Tidligere opprettet vi til og med en klasse som representerte en veterinærklinikk, som vi kunne sende et Animal
objekt til (forekomst av en foreldreklasse), og programmet behandlet dyret riktig basert på om det var en hund eller en katt. Selv om dette ikke er de enkleste oppgavene, er programmet i stand til å lære all nødvendig informasjon om klassene på kompileringstidspunktet. Følgelig, når du sender et Cat
objekt til metodene til veterinærklinikken i klassenmain()
metoden, vet programmet allerede at det er en katt, ikke en hund. La oss nå forestille oss at vi står overfor en annen oppgave. Målet vårt er å skrive en kodeanalysator. Vi må lage en CodeAnalyzer
klasse med en enkelt metode: void analyzeObject(Object o)
. Denne metoden bør:
- bestemme klassen til objektet som ble sendt til det og vis klassenavnet på konsollen;
- bestemme navnene på alle feltene i den beståtte klassen, inkludert private, og vis dem på konsollen;
- Bestem navnene på alle metodene for den beståtte klassen, inkludert private, og vis dem på konsollen.
public class CodeAnalyzer {
public static void analyzeClass(Object o) {
// Print the name of the class of object o
// Print the names of all variables of this class
// Print the names of all methods of this class
}
}
Nå kan vi tydelig se hvordan denne oppgaven skiller seg fra andre oppgaver som du har løst tidligere. Med vårt nåværende mål ligger vanskeligheten i det faktum at verken vi eller programmet vet nøyaktig hva som vil bli sendt tilanalyzeClass()
metode. Hvis du skriver et slikt program, vil andre programmerere begynne å bruke det, og de kan sende hva som helst til denne metoden - hvilken som helst standard Java-klasse eller hvilken som helst annen klasse de skriver. Den beståtte klassen kan ha et hvilket som helst antall variabler og metoder. Med andre ord, vi (og programmet vårt) har ingen anelse om hvilke klasser vi skal jobbe med. Men likevel må vi fullføre denne oppgaven. Og det er her standard Java Reflection API kommer oss til hjelp. Reflection API er et kraftig verktøy for språket. Oracles offisielle dokumentasjon anbefaler at denne mekanismen kun skal brukes av erfarne programmerere som vet hva de gjør. Du vil snart forstå hvorfor vi gir denne typen advarsel på forhånd :) Her er en liste over ting du kan gjøre med Reflection API:
- Identifiser/bestem klassen til et objekt.
- Få informasjon om klassemodifikatorer, felt, metoder, konstanter, konstruktører og superklasser.
- Finn ut hvilke metoder som tilhører et eller flere implementerte grensesnitt.
- Opprett en forekomst av en klasse hvis klassenavn ikke er kjent før programmet er kjørt.
- Hent og angi verdien til et forekomstfelt etter navn.
- Kall en forekomstmetode ved navn.
Hvordan identifisere/bestemme klassen til et objekt
La oss starte med det grunnleggende. Inngangspunktet til Java-refleksjonsmotoren er klassenClass
. Ja, det ser veldig morsomt ut, men det er det som er refleksjon :) Ved å bruke klassen Class
bestemmer vi først klassen til et objekt som sendes til metoden vår. La oss prøve å gjøre dette:
import learn.codegym.Cat;
public class CodeAnalyzer {
public static void analyzeClass(Object o) {
Class clazz = o.getClass();
System.out.println(clazz);
}
public static void main(String[] args) {
analyzeClass(new Cat("Fluffy", 6));
}
}
Konsoll utgang:
class learn.codegym.Cat
Vær oppmerksom på to ting. Først legger vi bevisst Cat
klassen i en egen learn.codegym
pakke. Nå kan du se at getClass()
metoden returnerer hele navnet på klassen. For det andre ga vi navnet variabelen vår clazz
. Det ser litt rart ut. Det ville være fornuftig å kalle det "klasse", men "klasse" er et reservert ord i Java. Kompilatoren vil ikke tillate at variabler kalles det. Vi måtte komme rundt det på en eller annen måte :) Ikke verst for en start! Hva mer hadde vi på listen over evner?
Hvordan få informasjon om klassemodifikatorer, felt, metoder, konstanter, konstruktører og superklasser.
Nå blir ting mer interessant! I den nåværende klassen har vi ingen konstanter eller en overordnet klasse. La oss legge dem til for å lage et komplett bilde. Lag den enklesteAnimal
foreldreklassen:
package learn.codegym;
public class Animal {
private String name;
private int age;
}
Og vi får Cat
klassen vår til å arve Animal
og legge til én konstant:
package learn.codegym;
public class Cat extends Animal {
private static final String ANIMAL_FAMILY = "Feline family";
private String name;
private int age;
// ...the rest of the class
}
Nå har vi det komplette bildet! La oss se hva refleksjon er i stand til :)
import learn.codegym.Cat;
import java.util.Arrays;
public class CodeAnalyzer {
public static void analyzeClass(Object o) {
Class clazz = o.getClass();
System.out.println("Class name: " + clazz);
System.out.println("Class fields: " + Arrays.toString(clazz.getDeclaredFields()));
System.out.println("Parent class: " + clazz.getSuperclass());
System.out.println("Class methods: " + Arrays.toString(clazz.getDeclaredMethods()));
System.out.println("Class constructors: " + Arrays.toString(clazz.getConstructors()));
}
public static void main(String[] args) {
analyzeClass(new Cat("Fluffy", 6));
}
}
Her er hva vi ser på konsollen:
Class name: class learn.codegym.Cat
Class fields: [private static final java.lang.String learn.codegym.Cat.ANIMAL_FAMILY, private java.lang.String learn.codegym.Cat.name, private int learn.codegym.Cat.age]
Parent class: class learn.codegym.Animal
Class methods: [public java.lang.String learn.codegym.Cat.getName(), public void learn.codegym.Cat.setName(java.lang.String), public void learn.codegym.Cat.sayMeow(), public void learn.codegym.Cat.setAge(int), public void learn.codegym.Cat.jump(), public int learn.codegym.Cat.getAge()]
Class constructors: [public learn.codegym.Cat(java.lang.String, int)]
Se på all den detaljerte klasseinformasjonen vi var i stand til å få! Og ikke bare offentlig informasjon, men også privat informasjon! Merk: private
variabler vises også i listen. Vår "analyse" av klassen kan i hovedsak anses som komplett: vi bruker metoden analyzeObject()
for å lære alt vi kan. Men dette er ikke alt vi kan gjøre med refleksjon. Vi er ikke begrenset til enkel observasjon – vi går videre til å ta grep! :)
Hvordan lage en forekomst av en klasse hvis klassenavn ikke er kjent før programmet er kjørt.
La oss starte med standardkonstruktøren. Klassen vårCat
har ikke en ennå, så la oss legge den til:
public Cat() {
}
Her er koden for å lage et Cat
objekt ved hjelp av refleksjon ( createCat()
metode):
import learn.codegym.Cat;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
public static Cat createCat() throws IOException, IllegalAccessException, InstantiationException, ClassNotFoundException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String className = reader.readLine();
Class clazz = Class.forName(className);
Cat cat = (Cat) clazz.newInstance();
return cat;
}
public static Object createObject() throws Exception {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String className = reader.readLine();
Class clazz = Class.forName(className);
Object result = clazz.newInstance();
return result;
}
public static void main(String[] args) throws IOException, IllegalAccessException, ClassNotFoundException, InstantiationException {
System.out.println(createCat());
}
}
Konsollinngang:
learn.codegym.Cat
Konsoll utgang:
Cat{name='null', age=0}
Dette er ikke en feil: verdiene til name
og age
vises på konsollen fordi vi skrev kode for å sende dem ut i toString()
metoden til Cat
klassen. Her leser vi navnet på en klasse hvis objekt vi skal lage fra konsollen. Programmet gjenkjenner navnet på klassen hvis objekt skal opprettes. 
newInstance()
metoden , lager vi et nytt objekt av denne klassen. Det er en annen sak omCat
konstruktør tar argumenter som input. La oss fjerne klassens standardkonstruktør og prøve å kjøre koden vår på nytt.
null
java.lang.InstantiationException: learn.codegym.Cat
at java.lang.Class.newInstance(Class.java:427)
Noe gikk galt! Vi fikk en feil fordi vi kalte en metode for å lage et objekt ved å bruke standardkonstruktøren. Men vi har ikke en slik konstruktør nå. Så når newInstance()
metoden kjører, bruker refleksjonsmekanismen vår gamle konstruktør med to parametere:
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
Men vi gjorde ikke noe med parameterne, som om vi helt hadde glemt dem! Å bruke refleksjon for å sende argumenter til konstruktøren krever litt "kreativitet":
import learn.codegym.Cat;
import java.lang.reflect.InvocationTargetException;
public class Main {
public static Cat createCat() {
Class clazz = null;
Cat cat = null;
try {
clazz = Class.forName("learn.codegym.Cat");
Class[] catClassParams = {String.class, int.class};
cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Fluffy", 6);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return cat;
}
public static void main(String[] args) {
System.out.println(createCat());
}
}
Konsoll utgang:
Cat{name='Fluffy', age=6}
La oss se nærmere på hva som skjer i programmet vårt. Vi laget en rekke Class
objekter.
Class[] catClassParams = {String.class, int.class};
De tilsvarer parameterne til konstruktøren vår (som bare har String
og int
parametere). Vi sender dem til clazz.getConstructor()
metoden og får tilgang til ønsket konstruktør. Etter det er alt vi trenger å gjøre å kalle newInstance()
metoden med de nødvendige argumentene, og ikke glem å eksplisitt kaste objektet til ønsket type: Cat
.
cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Fluffy", 6);
Nå er objektet vårt opprettet! Konsoll utgang:
Cat{name='Fluffy', age=6}
Går rett på vei :)
Hvordan hente og angi verdien av et forekomstfelt etter navn.
Tenk deg at du bruker en klasse skrevet av en annen programmerer. I tillegg har du ikke muligheten til å redigere den. For eksempel et ferdig klassebibliotek pakket i en JAR. Du kan lese koden til klassene, men du kan ikke endre den. Anta at programmereren som opprettet en av klassene i dette biblioteket (la det være vår gamleCat
klasse), og ikke fikk nok søvn natten før designet ble ferdigstilt, fjernet getter og setter for feltet age
. Nå har denne timen kommet til deg. Den oppfyller alle dine behov, siden du bare trenger Cat
objekter i programmet. Men du trenger at de har et age
felt! Dette er et problem: vi kan ikke nå feltet, fordi det harprivate
modifikator, og getter og setter ble slettet av den søvnberøvede utvikleren som opprettet klassen :/ Vel, refleksjon kan hjelpe oss i denne situasjonen! Vi har tilgang til koden for Cat
klassen, så vi kan i det minste finne ut hvilke felt den har og hva de heter. Med denne informasjonen kan vi løse problemet vårt:
import learn.codegym.Cat;
import java.lang.reflect.Field;
public class Main {
public static Cat createCat() {
Class clazz = null;
Cat cat = null;
try {
clazz = Class.forName("learn.codegym.Cat");
cat = (Cat) clazz.newInstance();
// We got lucky with the name field, since it has a setter
cat.setName("Fluffy");
Field age = clazz.getDeclaredField("age");
age.setAccessible(true);
age.set(cat, 6);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
return cat;
}
public static void main(String[] args) {
System.out.println(createCat());
}
}
Som det fremgår av kommentarene, name
er alt med feltet enkelt, siden klasseutviklerne ga en setter. Du vet allerede hvordan du lager objekter fra standardkonstruktører: vi har newInstance()
for dette. Men vi må fikse litt med det andre feltet. La oss finne ut hva som skjer her :)
Field age = clazz.getDeclaredField("age");
Her, ved å bruke vårt Class clazz
objekt , får vi tilgang til age
feltet via getDeclaredField()
metoden. Det lar oss få aldersfeltet som et Field age
objekt. Men dette er ikke nok, fordi vi ikke bare kan tildele verdier til private
felt. For å gjøre dette må vi gjøre feltet tilgjengelig ved å bruke metoden setAccessible()
:
age.setAccessible(true);
Når vi gjør dette til et felt, kan vi tilordne en verdi:
age.set(cat, 6);
Som du kan se har objektet vårt Field age
en slags inn- og ut-setter som vi sender en int-verdi til og objektet som skal tildeles feltet. Vi kjører main()
metoden vår og ser:
Cat{name='Fluffy', age=6}
Utmerket! Vi gjorde det! :) La oss se hva mer vi kan gjøre...
Hvordan kalle en instansmetode ved navn.
La oss endre litt på situasjonen i forrige eksempel. La oss si atCat
klasseutvikleren ikke gjorde en feil med getterne og setterne. Alt er greit i den forbindelse. Nå er problemet annerledes: det er en metode vi definitivt trenger, men utvikleren gjorde den privat:
private void sayMeow() {
System.out.println("Meow!");
}
Dette betyr at hvis vi lager Cat
objekter i programmet vårt, vil vi ikke kunne kalle sayMeow()
metoden på dem. Skal vi ha katter som ikke mjauer? Det er rart :/ Hvordan skal vi fikse dette? Nok en gang hjelper Reflection API oss! Vi vet navnet på metoden vi trenger. Alt annet er en teknisk sak:
import learn.codegym.Cat;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Main {
public static void invokeSayMeowMethod() {
Class clazz = null;
Cat cat = null;
try {
cat = new Cat("Fluffy", 6);
clazz = Class.forName(Cat.class.getName());
Method sayMeow = clazz.getDeclaredMethod("sayMeow");
sayMeow.setAccessible(true);
sayMeow.invoke(cat);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
invokeSayMeowMethod();
}
}
Her gjør vi mye av det samme som vi gjorde da vi fikk tilgang til et privat felt. Først får vi metoden vi trenger. Det er innkapslet i et Method
objekt:
Method sayMeow = clazz.getDeclaredMethod("sayMeow");
Metoden getDeclaredMethod()
lar oss komme til private metoder. Deretter gjør vi metoden kallbar:
sayMeow.setAccessible(true);
Og til slutt kaller vi metoden på ønsket objekt:
sayMeow.invoke(cat);
Her ser metodekallet vårt ut som et "callback": vi er vant til å bruke et punktum for å peke et objekt på ønsket metode ( cat.sayMeow()
), men når vi jobber med refleksjon, overfører vi til metoden objektet vi ønsker å kalle den metoden. Hva er på konsollen vår?
Meow!
Alt fungerte! :) Nå kan du se de enorme mulighetene som Javas refleksjonsmekanisme gir oss. I vanskelige og uventede situasjoner (som våre eksempler med en klasse fra et lukket bibliotek), kan det virkelig hjelpe oss mye. Men, som med enhver stormakt, medfører det stort ansvar. Ulempene med refleksjon er beskrevet i en spesiell del på Oracle-nettstedet. Det er tre hovedulemper:
-
Ytelsen er dårligere. Metoder som kalles å bruke refleksjon har dårligere ytelse enn metoder som kalles på vanlig måte.
-
Det er sikkerhetsrestriksjoner. Refleksjonsmekanismen lar oss endre et programs oppførsel under kjøring. Men på arbeidsplassen din, når du jobber med et virkelig prosjekt, kan du møte begrensninger som ikke tillater dette.
-
Risiko for eksponering av intern informasjon. Det er viktig å forstå at refleksjon er et direkte brudd på prinsippet om innkapsling: det lar oss få tilgang til private felt, metoder osv. Jeg tror ikke jeg trenger å nevne at et direkte og åpenbart brudd på prinsippene for OOP bør benyttes kun i de mest ekstreme tilfellene, når det ikke finnes andre måter å løse et problem på av årsaker utenfor din kontroll.
GO TO FULL VERSION