
Cat
klass:
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 allt om det, och du kan se de områden och metoder som det har. Anta att du plötsligt behöver introducera andra djurklasser i programmet. Du kan förmodligen skapa en klassarvsstruktur med en Animal
överordnad klass för enkelhetens skull. Tidigare skapade vi till och med en klass som representerade en veterinärklinik, till vilken vi kunde skicka ett Animal
objekt (instans av en föräldraklass), och programmet behandlade djuret på lämpligt sätt baserat på om det var en hund eller en katt. Även om detta inte är de enklaste uppgifterna, kan programmet lära sig all nödvändig information om klasserna vid kompileringstillfället. Följaktligen, när du skickar ett Cat
objekt till metoderna för veterinärkliniken klass imain()
metoden vet programmet redan att det är en katt, inte en hund. Låt oss nu föreställa oss att vi står inför en annan uppgift. Vårt mål är att skriva en kodanalysator. Vi måste skapa en CodeAnalyzer
klass med en enda metod: void analyzeObject(Object o)
. Denna metod bör:
- bestämma klassen för objektet som skickas till det och visa klassnamnet på konsolen;
- bestämma namnen på alla fält i den godkända klassen, inklusive privata, och visa dem på konsolen;
- bestäm namnen på alla metoder i den godkända klassen, inklusive privata, och visa dem på konsolen.
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
}
}
Nu kan vi tydligt se hur denna uppgift skiljer sig från andra uppgifter som du har löst tidigare. Med vårt nuvarande mål ligger svårigheten i det faktum att varken vi eller programmet vet exakt vad som kommer att skickas tillanalyzeClass()
metod. Om du skriver ett sådant program kommer andra programmerare att börja använda det, och de kan skicka vad som helst till den här metoden - vilken standard Java-klass som helst eller vilken annan klass de skriver. Den godkända klassen kan ha valfritt antal variabler och metoder. Med andra ord, vi (och vårt program) har ingen aning om vilka klasser vi kommer att arbeta med. Men ändå måste vi slutföra denna uppgift. Och det är här som standard Java Reflection API kommer till vår hjälp. Reflection API är ett kraftfullt verktyg för språket. Oracles officiella dokumentation rekommenderar att denna mekanism endast ska användas av erfarna programmerare som vet vad de gör. Du kommer snart att förstå varför vi ger den här typen av varningar i förväg :) Här är en lista över saker du kan göra med Reflection API:
- Identifiera/bestäm klassen för ett objekt.
- Få information om klassmodifierare, fält, metoder, konstanter, konstruktorer och superklasser.
- Ta reda på vilka metoder som hör till ett eller flera implementerade gränssnitt.
- Skapa en instans av en klass vars klassnamn inte är känt förrän programmet körs.
- Hämta och ställ in värdet på ett instansfält efter namn.
- Kalla en instansmetod vid namn.
Hur man identifierar/bestämmer klassen för ett objekt
Låt oss börja med grunderna. Ingångspunkten till Java-reflektionsmotorn ärClass
klassen. Ja, det ser riktigt roligt ut, men det är vad reflektion är :) Med hjälp av Class
klassen bestämmer vi först klassen för alla objekt som skickas till vår metod. Låt oss försöka göra det här:
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));
}
}
Konsolutgång:
class learn.codegym.Cat
Var uppmärksam på två saker. Först lägger vi medvetet Cat
klassen i ett separat learn.codegym
paket. Nu kan du se att getClass()
metoden returnerar klassens fullständiga namn. För det andra döpte vi vår variabel clazz
. Det ser lite konstigt ut. Det skulle vara vettigt att kalla det "klass", men "klass" är ett reserverat ord i Java. Kompilatorn tillåter inte att variabler kallas så. Vi var tvungna att komma runt det på något sätt :) Inte illa till att börja med! Vad mer hade vi på den listan över förmågor?
Hur man får information om klassmodifierare, fält, metoder, konstanter, konstruktorer och superklasser.
Nu blir allt mer intressant! I den aktuella klassen har vi inga konstanter eller en överordnad klass. Låt oss lägga till dem för att skapa en komplett bild. Skapa den enklasteAnimal
föräldraklassen:
package learn.codegym;
public class Animal {
private String name;
private int age;
}
Och vi kommer att få vår Cat
klass att ärva Animal
och lägga till en 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
}
Nu har vi hela bilden! Låt oss se vad reflektion är kapabel till :)
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));
}
}
Här är vad vi ser på konsolen:
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)]
Titta på all den detaljerade klassinformationen som vi kunde få! Och inte bara offentlig information, utan även privat information! Notera: private
variabler visas också i listan. Vår "analys" av klassen kan anses vara i stort sett fullständig: vi använder analyzeObject()
metoden för att lära oss allt vi kan. Men det här är inte allt vi kan göra med reflektion. Vi är inte begränsade till enkel observation – vi går vidare till att vidta åtgärder! :)
Hur man skapar en instans av en klass vars klassnamn inte är känt förrän programmet körs.
Låt oss börja med standardkonstruktorn. VårCat
klass har ingen ännu, så låt oss lägga till den:
public Cat() {
}
Här är koden för att skapa ett Cat
objekt med reflektion ( createCat()
metod):
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());
}
}
Konsolingång:
learn.codegym.Cat
Konsolutgång:
Cat{name='null', age=0}
Detta är inte ett fel: värdena för name
och age
visas på konsolen eftersom vi skrev kod för att mata ut dem i klassens toString()
metod . Cat
Här läser vi namnet på en klass vars objekt vi kommer att skapa från konsolen. Programmet känner igen namnet på den klass vars objekt ska skapas. 
newInstance()
metoden skapar vi ett nytt objekt av den här klassen. Det är en annan sak omCat
konstruktorn tar argument som input. Låt oss ta bort klassens standardkonstruktor och försöka köra vår kod igen.
null
java.lang.InstantiationException: learn.codegym.Cat
at java.lang.Class.newInstance(Class.java:427)
Något gick fel! Vi fick ett fel eftersom vi anropade en metod för att skapa ett objekt med standardkonstruktorn. Men vi har ingen sådan konstruktör nu. Så när newInstance()
metoden körs använder reflektionsmekanismen vår gamla konstruktor med två parametrar:
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
Men vi gjorde ingenting med parametrarna, som om vi helt hade glömt bort dem! Att använda reflektion för att skicka argument till konstruktören kräver lite "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());
}
}
Konsolutgång:
Cat{name='Fluffy', age=6}
Låt oss ta en närmare titt på vad som händer i vårt program. Vi skapade en rad Class
objekt.
Class[] catClassParams = {String.class, int.class};
De motsvarar parametrarna för vår konstruktör (som bara har String
och int
parametrar). Vi skickar dem till clazz.getConstructor()
metoden och får tillgång till önskad konstruktor. Efter det behöver vi bara anropa newInstance()
metoden med de nödvändiga argumenten, och glöm inte att explicit casta objektet till önskad typ: Cat
.
cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Fluffy", 6);
Nu har vårt objekt skapats framgångsrikt! Konsolutgång:
Cat{name='Fluffy', age=6}
Går direkt :)
Hur man hämtar och ställer in värdet på ett instansfält efter namn.
Föreställ dig att du använder en klass skriven av en annan programmerare. Dessutom har du inte möjlighet att redigera den. Till exempel ett färdigt klassbibliotek paketerat i en JAR. Du kan läsa koden för klasserna, men du kan inte ändra den. Anta att programmeraren som skapade en av klasserna i det här biblioteket (låt det vara vår gamlaCat
klass), som inte fick tillräckligt med sömn natten innan designen slutfördes, tog bort getter och seter för fältet age
. Nu har den här klassen kommit till dig. Det uppfyller alla dina behov, eftersom du bara behöver Cat
objekt i ditt program. Men du behöver dem för att ha ett age
fält! Detta är ett problem: vi kan inte nå fältet, eftersom det harprivate
modifierare, och getter och setter raderades av den sömnberövade utvecklaren som skapade klassen :/ Tja, reflektion kan hjälpa oss i den här situationen! Vi har tillgång till koden för Cat
klassen, så vi kan åtminstone ta reda på vilka fält den har och vad de heter. Med denna information kan vi lösa vårt problem:
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 sägs i kommentarerna name
är allt med fältet okomplicerat, eftersom klassutvecklarna gav en uppsättare. Du vet redan hur man skapar objekt från standardkonstruktörer: vi har newInstance()
för detta. Men vi måste pyssla lite med det andra fältet. Låt oss ta reda på vad som händer här :)
Field age = clazz.getDeclaredField("age");
Här, med hjälp av vårt Class clazz
objekt , kommer vi åt age
fältet via getDeclaredField()
metoden. Det låter oss få åldersfältet som ett Field age
objekt. Men detta räcker inte, eftersom vi inte bara kan tilldela värden till private
fält. För att göra detta måste vi göra fältet tillgängligt genom att använda setAccessible()
metoden:
age.setAccessible(true);
När vi gör detta till ett fält kan vi tilldela ett värde:
age.set(cat, 6);
Som du kan se har vårt Field age
objekt en slags inifrån-ut-sättare till vilken vi skickar ett int-värde och det objekt vars fält ska tilldelas. Vi kör vår main()
metod och ser:
Cat{name='Fluffy', age=6}
Excellent! Vi gjorde det! :) Låt oss se vad mer vi kan göra...
Hur man kallar en instansmetod vid namn.
Låt oss ändra situationen något i föregående exempel. Låt oss säga attCat
klassutvecklaren inte gjorde ett misstag med getters och seters. Allt är okej i det avseendet. Nu är problemet ett annat: det finns en metod som vi definitivt behöver, men utvecklaren gjorde den privat:
private void sayMeow() {
System.out.println("Meow!");
}
Det betyder att om vi skapar Cat
objekt i vårt program kommer vi inte att kunna anropa sayMeow()
metoden på dem. Kommer vi att ha katter som inte jamar? Det är konstigt :/ Hur skulle vi fixa detta? Återigen hjälper Reflection API oss! Vi vet namnet på metoden vi behöver. Allt annat är en teknikalitet:
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();
}
}
Här gör vi mycket av samma sak som vi gjorde när vi fick tillgång till ett privat fält. Först får vi den metod vi behöver. Det är inkapslat i ett Method
objekt:
Method sayMeow = clazz.getDeclaredMethod("sayMeow");
Metoden getDeclaredMethod()
låter oss komma till privata metoder. Därefter gör vi metoden anropbar:
sayMeow.setAccessible(true);
Och slutligen kallar vi metoden på det önskade objektet:
sayMeow.invoke(cat);
Här ser vårt metodanrop ut som ett "återuppringning": vi är vana vid att använda en punkt för att peka ett objekt på den önskade metoden ( ), cat.sayMeow()
men när vi arbetar med reflektion överför vi det objekt som vi vill anropa till metoden. den metoden. Vad finns på vår konsol?
Meow!
Allt fungerade! :) Nu kan du se de stora möjligheter som Javas reflektionsmekanism ger oss. I svåra och oväntade situationer (som våra exempel med en klass från ett stängt bibliotek) kan det verkligen hjälpa oss mycket. Men som med vilken stormakt som helst, medför det stort ansvar. Nackdelarna med reflektion beskrivs i ett särskilt avsnitt på Oracles hemsida. Det finns tre huvudsakliga nackdelar:
-
Prestanda är sämre. Metoder som kallas att använda reflektion har sämre prestanda än metoder som kallas på vanligt sätt.
-
Det finns säkerhetsrestriktioner. Reflexionsmekanismen låter oss ändra ett programs beteende under körning. Men på din arbetsplats, när du arbetar med ett riktigt projekt, kan du möta begränsningar som inte tillåter detta.
-
Risk för exponering av intern information. Det är viktigt att förstå att reflektion är ett direkt brott mot principen om inkapsling: den ger oss tillgång till privata områden, metoder etc. Jag tror inte att jag behöver nämna att ett direkt och flagrant brott mot principerna för OOP bör tillgripas endast i de mest extrema fallen, när det inte finns några andra sätt att lösa ett problem av skäl utanför din kontroll.
GO TO FULL VERSION