JavaRush /Java-Blog /Random-DE /Reflexion in Java – Anwendungsbeispiele

Reflexion in Java – Anwendungsbeispiele

Veröffentlicht in der Gruppe Random-DE
Vielleicht ist Ihnen das Konzept der „Reflexion“ im Alltag schon einmal begegnet. Normalerweise bezieht sich dieses Wort auf den Prozess des Selbststudiums. In der Programmierung hat es eine ähnliche Bedeutung – es ist ein Mechanismus zum Untersuchen von Daten über ein Programm sowie zum Ändern der Struktur und des Verhaltens des Programms während seiner Ausführung. Wichtig hierbei ist, dass dies zur Laufzeit und nicht zur Kompilierzeit erfolgt. Aber warum den Code zur Laufzeit untersuchen? Sie sehen es bereits :/ Beispiele für die Verwendung von Reflection – 1Die Idee der Reflexion ist aus einem Grund möglicherweise nicht sofort klar: Bis zu diesem Moment kannten Sie immer die Klassen, mit denen Sie arbeiteten. Nun, Sie könnten zum Beispiel eine Klasse schreiben Cat:
package learn.javarush;

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 +
           '}';
}

}
Sie wissen alles darüber, sehen, welche Felder und Methoden es hat. Sicherlich können Sie der Einfachheit halber ein Vererbungssystem mit einer gemeinsamen Klasse erstellen Animal, wenn das Programm plötzlich andere Tierklassen benötigt. Zuvor haben wir sogar eine Tierklinikklasse erstellt, in der Sie ein übergeordnetes Objekt übergeben konnten Animalund das Programm das Tier abhängig davon behandelte, ob es ein Hund oder eine Katze war. Obwohl diese Aufgaben nicht sehr einfach sind, lernt das Programm zur Kompilierungszeit alle benötigten Informationen über die Klassen. Wenn Sie also ein Objekt in einer Methode an die Methoden der Klasse „Veterinärklinik“ main()übergeben , weiß das Programm bereits, dass es sich um eine Katze und nicht um einen Hund handelt. CatStellen wir uns nun vor, dass wir vor einer anderen Aufgabe stehen. Unser Ziel ist es, einen Code-Analysator zu schreiben. Wir müssen eine Klasse CodeAnalyzermit einer einzigen Methode erstellen – void analyzeClass(Object o). Diese Methode sollte:
  • Bestimmen Sie, an welche Klasse das Objekt übergeben wurde, und zeigen Sie den Klassennamen in der Konsole an.
  • Ermitteln Sie die Namen aller Felder dieser Klasse, einschließlich privater, und zeigen Sie sie in der Konsole an.
  • Ermitteln Sie die Namen aller Methoden dieser Klasse, einschließlich privater, und zeigen Sie sie in der Konsole an.
Es wird ungefähr so ​​aussehen:
public class CodeAnalyzer {

   public static void analyzeClass(Object o) {

       //Вывести название класса, к которому принадлежит ein Objekt o
       //Вывести названия всех переменных этого класса
       //Вывести названия всех методов этого класса
   }

}
Jetzt ist der Unterschied zwischen diesem Problem und den anderen Problemen, die Sie zuvor gelöst haben, sichtbar. Die Schwierigkeit liegt in diesem Fall darin, dass weder Sie noch das Programm wissen, was genau an die Methode übergeben wird analyzeClass(). Sie schreiben ein Programm, andere Programmierer werden es verwenden und können dieser Methode alles übergeben – jede Standard-Java-Klasse oder jede Klasse, die sie geschrieben haben. Diese Klasse kann beliebig viele Variablen und Methoden haben. Mit anderen Worten: In diesem Fall haben wir (und unser Programm) keine Ahnung, mit welchen Klassen wir arbeiten werden. Und doch müssen wir dieses Problem lösen. Und hier kommt uns die Standard-Java-Bibliothek zu Hilfe – die Java Reflection API. Die Reflection API ist eine leistungsstarke Sprachfunktion. In der offiziellen Oracle-Dokumentation heißt es, dass dieser Mechanismus nur erfahrenen Programmierern empfohlen wird, die sehr gut verstehen, was sie tun. Sie werden schnell verstehen, warum wir plötzlich solche Warnungen im Voraus erhalten :) Hier ist eine Liste, was mit der Reflection API gemacht werden kann:
  1. Finden/bestimmen Sie die Klasse eines Objekts.
  2. Erhalten Sie Informationen zu Klassenmodifikatoren, Feldern, Methoden, Konstanten, Konstruktoren und Superklassen.
  3. Finden Sie heraus, welche Methoden zur implementierten Schnittstelle/Schnittstellen gehören.
  4. Erstellen Sie eine Instanz einer Klasse, wenn der Klassenname bis zur Ausführung des Programms unbekannt ist.
  5. Rufen Sie den Wert eines Objektfelds anhand des Namens ab und legen Sie ihn fest.
  6. Rufen Sie die Methode eines Objekts nach Namen auf.
Beeindruckende Liste, oder? :) :) Passt auf:Der Reflexionsmechanismus ist in der Lage, all dies „on the fly“ zu erledigen, unabhängig davon, welches Klassenobjekt wir an unseren Codeanalysator übergeben! Sehen wir uns anhand von Beispielen die Funktionen der Reflection-API an.

So ermitteln/bestimmen Sie die Klasse eines Objekts

Beginnen wir mit den Grundlagen. Der Einstiegspunkt zum Reflexionsmechanismus von Java ist die Class. Ja, es sieht wirklich lustig aus, aber genau dafür ist Reflektion da :) Mithilfe einer Klasse Classbestimmen wir zunächst die Klasse eines beliebigen Objekts, das an unsere Methode übergeben wird. Lass uns das versuchen:
import learn.javarush.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("Barsik", 6));
   }
}
Konsolenausgabe:

class learn.javarush.Cat
Achten Sie auf zwei Dinge. Erstens haben wir die Klasse bewusst Catin ein separates Paket gepackt. learn.javarush;Jetzt können Sie sehen, dass sie getClass()den vollständigen Namen der Klasse zurückgibt. Zweitens haben wir unsere Variable benannt clazz. Sieht etwas seltsam aus. Natürlich sollte es „Klasse“ heißen, aber „Klasse“ ist ein reserviertes Wort in der Java-Sprache und der Compiler lässt nicht zu, dass Variablen auf diese Weise aufgerufen werden. Ich musste da raus :) Nun ja, kein schlechter Anfang! Was hatten wir sonst noch auf der Liste der Möglichkeiten?

So erhalten Sie Informationen zu Klassenmodifikatoren, Feldern, Methoden, Konstanten, Konstruktoren und Superklassen

Das ist schon interessanter! In der aktuellen Klasse haben wir keine Konstanten und keine übergeordnete Klasse. Fügen wir sie der Vollständigkeit halber hinzu. Lassen Sie uns die einfachste übergeordnete Klasse erstellen Animal:
package learn.javarush;
public class Animal {

   private String name;
   private int age;
}
Und fügen wir unserer Klasse Cateine Vererbung von und eine Konstante hinzu:Animal
package learn.javarush;

public class Cat extends Animal {

   private static final String ANIMAL_FAMILY = "Семейство кошачьих";

   private String name;
   private int age;

   //...остальная часть класса
}
Jetzt haben wir ein komplettes Set! Lasst uns die Möglichkeiten der Reflexion ausprobieren :)
import learn.javarush.Cat;

import java.util.Arrays;

public class CodeAnalyzer {

   public static void analyzeClass(Object o) {
       Class clazz = o.getClass();
       System.out.println("Name класса: " + clazz);
       System.out.println("Поля класса: " + Arrays.toString(clazz.getDeclaredFields()));
       System.out.println("Родительский класс: " + clazz.getSuperclass());
       System.out.println("Методы класса: " +  Arrays.toString(clazz.getDeclaredMethods()));
       System.out.println("Конструкторы класса: " + Arrays.toString(clazz.getConstructors()));
   }

   public static void main(String[] args) {

       analyzeClass(new Cat("Barsik", 6));
   }
}
Das bekommen wir in der Konsole:
Name класса: class learn.javarush.Cat
Поля класса: [private static final java.lang.String learn.javarush.Cat.ANIMAL_FAMILY, private java.lang.String learn.javarush.Cat.name, private int learn.javarush.Cat.age]
Родительский класс: class learn.javarush.Animal
Методы класса: [public java.lang.String learn.javarush.Cat.getName(), public void learn.javarush.Cat.setName(java.lang.String), public void learn.javarush.Cat.sayMeow(), public void learn.javarush.Cat.setAge(int), public void learn.javarush.Cat.jump(), public int learn.javarush.Cat.getAge()]
Конструкторы класса: [public learn.javarush.Cat(java.lang.String,int)]
Wir haben so viele detaillierte Informationen über den Kurs erhalten! Und zwar nicht nur um öffentliche, sondern auch um private Teile. Passt auf: private-Variablen werden ebenfalls in der Liste angezeigt. Tatsächlich kann die „Analyse“ der Klasse an dieser Stelle als abgeschlossen betrachtet werden: Jetzt analyzeClass()werden wir mit der Methode alles lernen, was möglich ist. Aber das sind nicht alle Möglichkeiten, die uns die Arbeit mit Reflexion bietet. Beschränken wir uns nicht auf die einfache Beobachtung und gehen wir zum aktiven Handeln über! :) :)

So erstellen Sie eine Instanz einer Klasse, wenn der Klassenname vor der Ausführung des Programms unbekannt ist

Beginnen wir mit dem Standardkonstruktor. Es ist noch nicht in unserer Klasse Cat, also fügen wir es hinzu:
public Cat() {

}
So würde der Code zum Erstellen eines Objekts Catmithilfe von Reflektion (Methode createCat()) aussehen:
import learn.javarush.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());
   }
}
Geben Sie in die Konsole ein:

learn.javarush.Cat
Konsolenausgabe:

Cat{name='null', age=0}
Dies ist kein Fehler: Die Werte nameund agewerden in der Konsole angezeigt, da wir ihre Ausgabe in der toString()Klassenmethode programmiert haben Cat. Hier lesen wir den Namen der Klasse, deren Objekt wir über die Konsole erstellen werden. Das laufende Programm lernt den Namen der Klasse, deren Objekt es erstellen wird. Beispiele für die Verwendung von Reflection - 3Der Kürze halber haben wir den Code zur ordnungsgemäßen Ausnahmebehandlung weggelassen, damit er nicht mehr Platz einnimmt als das Beispiel selbst. In einem echten Programm lohnt es sich natürlich auf jeden Fall, mit Situationen umzugehen, in denen falsche Namen usw. eingegeben werden. Der Standardkonstruktor ist eine ziemlich einfache Sache, daher ist es, wie Sie sehen, nicht schwierig, mit ihm eine Instanz einer Klasse zu erstellen :) Und mit der Methode newInstance()erstellen wir ein neues Objekt dieser Klasse. Eine andere Sache ist es, wenn der Klassenkonstruktor CatParameter als Eingabe akzeptiert. Entfernen wir den Standardkonstruktor aus der Klasse und versuchen wir erneut, unseren Code auszuführen.

null
java.lang.InstantiationException: learn.javarush.Cat
  at java.lang.Class.newInstance(Class.java:427)
Etwas ist schief gelaufen! Wir haben eine Fehlermeldung erhalten, weil wir eine Methode zum Erstellen eines Objekts über den Standardkonstruktor aufgerufen haben. Aber jetzt haben wir keinen solchen Designer. Das heißt, wenn die Methode funktioniert, newInstance()verwendet der Reflexionsmechanismus unseren alten Konstruktor mit zwei Parametern:
public Cat(String name, int age) {
   this.name = name;
   this.age = age;
}
Aber wir haben mit den Parametern nichts gemacht, als hätten wir sie völlig vergessen! Um sie mittels Reflektion an den Konstruktor zu übergeben, müssen Sie es ein wenig anpassen:
import learn.javarush.Cat;

import java.lang.reflect.InvocationTargetException;

public class Main {

   public static Cat createCat()  {

       Class clazz = null;
       Cat cat = null;

       try {
           clazz = Class.forName("learn.javarush.Cat");
           Class[] catClassParams = {String.class, int.class};
           cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Barsik", 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());
   }
}
Konsolenausgabe:

Cat{name='Barsik', age=6}
Werfen wir einen genaueren Blick auf das, was in unserem Programm passiert. Wir haben ein Array von Objekten erstellt Class.
Class[] catClassParams = {String.class, int.class};
Sie entsprechen den Parametern unseres Konstruktors (wir haben nur die Parameter Stringund int). Wir übergeben sie an die Methode clazz.getConstructor()und erhalten Zugriff auf den erforderlichen Konstruktor. Danach müssen Sie nur noch die Methode newInstance()mit den erforderlichen Parametern aufrufen und nicht vergessen, das Objekt explizit in die von uns benötigte Klasse umzuwandeln - Cat.
cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Barsik", 6);
Als Ergebnis wird unser Objekt erfolgreich erstellt! Konsolenausgabe:

Cat{name='Barsik', age=6}
Lass uns weitermachen :)

So erhalten und legen Sie den Wert eines Objektfelds anhand des Namens fest

Stellen Sie sich vor, Sie verwenden eine Klasse, die von einem anderen Programmierer geschrieben wurde. Sie haben jedoch keine Möglichkeit, es zu bearbeiten. Zum Beispiel eine vorgefertigte Klassenbibliothek, verpackt in einem JAR. Sie können den Klassencode lesen, aber nicht ändern. Der Programmierer, der die Klasse in dieser Bibliothek erstellt hat (sei es unsere alte Klasse Cat), bekam vor dem endgültigen Entwurf nicht genug Schlaf und entfernte die Getter und Setter für das Feld age. Jetzt ist dieser Kurs zu Ihnen gekommen. Es erfüllt Ihre Anforderungen voll und ganz, da Sie nur Objekte im Programm benötigen Cat. Aber Sie brauchen sie mit demselben Feld age! Das ist ein Problem: Wir können das Feld nicht erreichen, weil es einen Modifikator hat privateund die Getter und Setter vom potenziellen Entwickler dieser Klasse entfernt wurden :/ Nun, Reflexion kann uns auch in dieser Situation helfen! CatWir haben Zugriff auf den Klassencode : Wir können zumindest herausfinden, welche Felder er hat und wie sie heißen. Mit diesen Informationen lösen wir unser Problem:
import learn.javarush.Cat;

import java.lang.reflect.Field;

public class Main {

   public static Cat createCat()  {

       Class clazz = null;
       Cat cat = null;
       try {
           clazz = Class.forName("learn.javarush.Cat");
           cat = (Cat) clazz.newInstance();

           //с полем name нам повезло - для него в классе есть setter
           cat.setName("Barsik");

           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());
   }
}
Wie im Kommentar erwähnt, nameist mit dem Feld alles einfach: Die Klassenentwickler haben einen Setter dafür bereitgestellt. Sie wissen auch bereits, wie Sie Objekte aus Standardkonstruktoren erstellen: Dafür gibt es eine Methode newInstance(). Aber Sie müssen am zweiten Feld herumbasteln. Lasst uns herausfinden, was hier los ist :)
Field age = clazz.getDeclaredField("age");
Hier greifen wir mithilfe unseres Objekts auf das Class clazzFeld zu . Es gibt uns die Möglichkeit, das Altersfeld als Objekt zu erhalten . Dies reicht jedoch noch nicht aus, da Felder nicht einfach mit Werten belegt werden können. Dazu müssen Sie das Feld mit der Methode „verfügbar“ machen : agegetDeclaredField()Field ageprivatesetAccessible()
age.setAccessible(true);
Den Feldern, für die dies geschieht, können Werte zugewiesen werden:
age.set(cat, 6);
Wie Sie sehen, haben wir eine Art Setter auf den Kopf gestellt: Wir weisen dem Feld Field ageseinen Wert zu und übergeben ihm auch das Objekt, dem dieses Feld zugewiesen werden soll. Lassen Sie uns unsere Methode ausführen main()und sehen:

Cat{name='Barsik', age=6}
Großartig, wir haben alles geschafft! :) Mal sehen, welche anderen Möglichkeiten wir haben...

So rufen Sie die Methode eines Objekts nach Namen auf

Lassen Sie uns die Situation gegenüber dem vorherigen Beispiel leicht ändern. Nehmen wir an, der Klassenentwickler Cathat einen Fehler mit den Feldern gemacht – beide sind verfügbar, es gibt Getter und Setter für sie, alles ist in Ordnung. Das Problem ist anders: Er hat private zu einer Methode gemacht, die wir unbedingt brauchen:
private void sayMeow() {

   System.out.println("Meow!");
}
Infolgedessen erstellen wir Objekte Catin unserem Programm, können deren Methode jedoch nicht aufrufen sayMeow(). Werden wir Katzen haben, die nicht miauen? Ziemlich seltsam :/ Wie kann ich das beheben? Wieder einmal kommt die Reflection API zur Rettung! Wir kennen den Namen der erforderlichen Methode. Der Rest ist eine Frage der Technik:
import learn.javarush.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("Barsik", 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();
   }
}
Hier verhalten wir uns ähnlich wie beim Zugriff auf ein privates Feld. Zuerst erhalten wir die von uns benötigte Methode, die in einem Klassenobjekt gekapselt ist Method:
Method sayMeow = clazz.getDeclaredMethod("sayMeow");
Mit Hilfe getDeclaredMethod()können Sie private Methoden „erreichen“. Als nächstes machen wir die Methode aufrufbar:
sayMeow.setAccessible(true);
Und schließlich rufen wir die Methode für das gewünschte Objekt auf:
sayMeow.invoke(cat);
Der Aufruf einer Methode sieht auch wie ein „Rückwärtsaufruf“ aus: Wir sind es gewohnt, ein Objekt mit einem Punkt ( cat.sayMeow()) auf die gewünschte Methode zu verweisen, und wenn wir mit Reflektion arbeiten, übergeben wir der Methode das Objekt, von dem aus es aufgerufen werden muss . Was haben wir in der Konsole?

Meow!
Es hat alles geklappt! :) Jetzt sehen Sie, welche umfangreichen Möglichkeiten uns der Reflexionsmechanismus in Java bietet. In schwierigen und unerwarteten Situationen (wie in den Beispielen mit einer Klasse aus einer geschlossenen Bibliothek) kann es uns wirklich sehr helfen. Allerdings bringt sie, wie jede Großmacht, auch große Verantwortung mit sich. Über die Nachteile der Reflexion wird in einem speziellen Abschnitt auf der Oracle-Website berichtet . Es gibt drei Hauptnachteile:
  1. Die Produktivität nimmt ab. Methoden, die mithilfe von Reflektion aufgerufen werden, weisen eine geringere Leistung auf als Methoden, die normal aufgerufen werden.

  2. Es gibt Sicherheitsbeschränkungen. Mit dem Reflexionsmechanismus können Sie das Verhalten des Programms während der Laufzeit ändern. Aber in Ihrer Arbeitsumgebung an einem realen Projekt kann es Einschränkungen geben, die Ihnen dies nicht erlauben.

  3. Risiko der Offenlegung von Insiderinformationen. Es ist wichtig zu verstehen, dass die Verwendung von Reflektion direkt gegen das Prinzip der Kapselung verstößt: Sie ermöglicht uns den Zugriff auf private Felder, Methoden usw. Ich denke, dass es nicht nötig ist zu erklären, dass auf direkte und grobe Verstöße gegen die OOP-Prinzipien nur in den extremsten Fällen zurückgegriffen werden sollte, wenn es aus Gründen, die außerhalb Ihrer Kontrolle liegen, keine anderen Möglichkeiten gibt, das Problem zu lösen.

Setzen Sie den Reflexionsmechanismus mit Bedacht und nur in Situationen ein, in denen er nicht vermieden werden kann, und vergessen Sie nicht seine Mängel. Damit ist unser Vortrag abgeschlossen! Es ist ziemlich groß geworden, aber heute hast du viel Neues gelernt :)
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION