CodeGym /Java-Blog /Random-DE /Beispiele zur Reflexion
Autor
Alex Vypirailenko
Java Developer at Toshiba Global Commerce Solutions

Beispiele zur Reflexion

Veröffentlicht in der Gruppe Random-DE
Vielleicht ist Ihnen das Konzept der „Reflexion“ im alltäglichen Leben schon einmal begegnet. Dieses Wort bezieht sich normalerweise auf den Prozess des Selbststudiums. In der Programmierung hat es eine ähnliche Bedeutung – es ist ein Mechanismus zum Analysieren von Daten über ein Programm und sogar zum Ändern der Struktur und des Verhaltens eines Programms, während das Programm ausgeführt wird. Reflexionsbeispiele - 1 Wichtig dabei ist, dass wir dies zur Laufzeit tun, nicht zur Kompilierungszeit. Aber warum den Code zur Laufzeit untersuchen? Schließlich können Sie den Code bereits lesen :/ Es gibt einen Grund, warum die Idee der Reflexion möglicherweise nicht sofort klar ist: Bis zu diesem Zeitpunkt wussten Sie immer, mit welchen Klassen Sie arbeiteten. Sie könnten beispielsweise eine CatKlasse schreiben:

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

}
Sie wissen alles darüber und können die darin enthaltenen Felder und Methoden sehen. Angenommen, Sie müssen plötzlich andere Tierklassen in das Programm einführen. AnimalDer Einfachheit halber könnten Sie wahrscheinlich eine Klassenvererbungsstruktur mit einer übergeordneten Klasse erstellen . Zuvor haben wir sogar eine Klasse erstellt, die eine Tierklinik darstellt, an die wir ein AnimalObjekt übergeben konnten (Instanz einer übergeordneten Klasse), und das Programm behandelte das Tier entsprechend, je nachdem, ob es ein Hund oder eine Katze war. Auch wenn dies nicht die einfachsten Aufgaben sind, ist das Programm in der Lage, alle notwendigen Informationen über die Klassen zur Kompilierungszeit zu lernen. CatDementsprechend, wenn Sie ein Objekt an die Methoden der Tierklinik-Klasse in der übergebenmain()Methode weiß das Programm bereits, dass es sich um eine Katze und nicht um einen Hund handelt. Stellen wir uns nun vor, dass wir vor einer anderen Aufgabe stehen. Unser Ziel ist es, einen Code-Analysator zu schreiben. Wir müssen eine CodeAnalyzerKlasse mit einer einzigen Methode erstellen: void analyzeObject(Object o). Diese Methode sollte:
  • Bestimmen Sie die Klasse des an ihn übergebenen Objekts und zeigen Sie den Klassennamen auf der Konsole an.
  • Ermitteln Sie die Namen aller Felder der übergebenen Klasse, einschließlich privater Felder, und zeigen Sie sie auf der Konsole an.
  • Ermitteln Sie die Namen aller Methoden der übergebenen Klasse, einschließlich privater, und zeigen Sie sie auf der Konsole an.
Es wird ungefähr so ​​aussehen:

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
   }
  
}
Jetzt können wir deutlich erkennen, wie sich diese Aufgabe von anderen Aufgaben unterscheidet, die Sie zuvor gelöst haben. Bei unserem aktuellen Ziel liegt die Schwierigkeit darin, dass weder wir noch das Programm wissen, was genau an den übergeben wirdanalyzeClass()Methode. Wenn Sie ein solches Programm schreiben, werden andere Programmierer es verwenden und möglicherweise alles an diese Methode übergeben – jede Standard-Java-Klasse oder jede andere Klasse, die sie schreiben. Die übergebene Klasse kann beliebig viele Variablen und Methoden haben. Mit anderen Worten: Wir (und unser Programm) haben keine Ahnung, mit welchen Klassen wir arbeiten werden. Dennoch müssen wir diese Aufgabe erfüllen. Und hier kommt uns die Standard-Java-Reflection-API zu Hilfe. Die Reflection API ist ein leistungsstarkes Werkzeug der Sprache. In der offiziellen Dokumentation von Oracle wird empfohlen, diesen Mechanismus nur von erfahrenen Programmierern zu verwenden, die wissen, was sie tun. Sie werden bald verstehen, warum wir diese Art von Warnung im Voraus ausgeben :) Hier ist eine Liste der Dinge, die Sie mit der Reflection API tun können:
  1. Identifizieren/bestimmen Sie die Klasse eines Objekts.
  2. Erhalten Sie Informationen zu Klassenmodifikatoren, Feldern, Methoden, Konstanten, Konstruktoren und Superklassen.
  3. Finden Sie heraus, welche Methoden zu einer oder mehreren implementierten Schnittstellen gehören.
  4. Erstellen Sie eine Instanz einer Klasse, deren Klassenname erst bekannt ist, wenn das Programm ausgeführt wird.
  5. Rufen Sie den Wert eines Instanzfelds anhand des Namens ab und legen Sie ihn fest.
  6. Rufen Sie eine Instanzmethode nach Namen auf.
Beeindruckende Liste, oder? :) :) Notiz:Der Reflexionsmechanismus kann all diese Dinge „on the fly“ erledigen, unabhängig von der Art des Objekts, das wir an unseren Codeanalysator übergeben! Lassen Sie uns die Funktionen der Reflection-API anhand einiger Beispiele erkunden.

So identifizieren/bestimmen Sie die Klasse eines Objekts

Beginnen wir mit den Grundlagen. Der Einstiegspunkt zur Java Reflection Engine ist die ClassKlasse. Ja, es sieht wirklich lustig aus, aber genau das ist Reflektion :) Mithilfe der ClassKlasse bestimmen wir zunächst die Klasse eines beliebigen Objekts, das an unsere Methode übergeben wird. Versuchen wir Folgendes:

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));
   }
}
Konsolenausgabe:

class learn.codegym.Cat
Achten Sie auf zwei Dinge. Zunächst haben wir die CatKlasse bewusst in ein separates learn.codegymPaket gepackt. Jetzt können Sie sehen, dass die getClass()Methode den vollständigen Namen der Klasse zurückgibt. Zweitens haben wir unsere Variable benannt clazz. Das sieht etwas seltsam aus. Es wäre sinnvoll, es „Klasse“ zu nennen, aber „Klasse“ ist in Java ein reserviertes Wort. Der Compiler lässt nicht zu, dass Variablen so aufgerufen werden. Das mussten wir irgendwie umgehen :) Nicht schlecht für den Anfang! Was stand sonst noch auf dieser Liste von Fähigkeiten?

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

Jetzt wird es interessanter! In der aktuellen Klasse haben wir keine Konstanten oder eine übergeordnete Klasse. Fügen wir sie hinzu, um ein vollständiges Bild zu erstellen. Erstellen Sie die einfachste Animalübergeordnete Klasse:

package learn.codegym;
public class Animal {

   private String name;
   private int age;
}
Und wir lassen unsere CatKlasse erben Animalund fügen eine Konstante hinzu:

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
}
Jetzt haben wir das vollständige Bild! Mal sehen, wozu Reflexion fähig ist :)

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));
   }
}
Folgendes sehen wir auf der Konsole:

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)]
Schauen Sie sich all die detaillierten Kursinformationen an, die wir erhalten konnten! Und zwar nicht nur öffentliche Informationen, sondern auch private Informationen! Notiz: privateVariablen werden ebenfalls in der Liste angezeigt. Unsere „Analyse“ der Klasse kann als im Wesentlichen abgeschlossen betrachtet werden: Wir verwenden die analyzeObject()Methode, um alles zu lernen, was wir können. Aber das ist nicht alles, was wir mit Reflexion erreichen können. Wir beschränken uns nicht nur auf die bloße Beobachtung – wir gehen dazu über, Maßnahmen zu ergreifen! :) :)

So erstellen Sie eine Instanz einer Klasse, deren Klassenname erst bekannt ist, wenn das Programm ausgeführt wird.

Beginnen wir mit dem Standardkonstruktor. Unsere CatKlasse hat noch keins, also fügen wir es hinzu:

public Cat() {
  
}
Hier ist der Code zum Erstellen eines CatObjekts mithilfe der Reflection- createCat()Methode ():

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());
   }
}
Konsoleneingabe:

learn.codegym.Cat
Konsolenausgabe:

Cat{name='null', age=0}
Dies ist kein Fehler: Die Werte von nameund agewerden auf der Konsole angezeigt, da wir Code geschrieben haben, um sie in der toString()Methode der CatKlasse auszugeben. Hier lesen wir den Namen einer Klasse, deren Objekt wir über die Konsole erstellen werden. Das Programm erkennt den Namen der Klasse, deren Objekt erstellt werden soll. Beispiele zur Reflexion - 3Der Kürze halber haben wir den richtigen Ausnahmebehandlungscode weggelassen, der mehr Platz beanspruchen würde als das Beispiel selbst. In einem echten Programm sollten Sie natürlich mit Situationen umgehen, in denen es um falsch eingegebene Namen usw. geht. Der Standardkonstruktor ist ziemlich einfach, sodass Sie ihn, wie Sie sehen, leicht zum Erstellen einer Instanz der Klasse verwenden können :) Verwenden der newInstance()Methode , erstellen wir ein neues Objekt dieser Klasse. Eine andere Sache ist es, wenn dieCatDer Konstruktor akzeptiert Argumente als Eingabe. Entfernen wir den Standardkonstruktor der Klasse und versuchen wir erneut, unseren Code auszuführen.

null
java.lang.InstantiationException: learn.codegym.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 mit dem Standardkonstruktor aufgerufen haben. Aber wir haben jetzt keinen solchen Konstruktor. Wenn die newInstance()Methode ausgeführt wird, 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! Die Verwendung von Reflektion zur Übergabe von Argumenten an den Konstruktor erfordert ein wenig „Kreativität“:

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());
   }
}
Konsolenausgabe:

Cat{name='Fluffy', age=6}
Werfen wir einen genaueren Blick auf das, was in unserem Programm passiert. Wir haben eine Reihe von ClassObjekten erstellt.

Class[] catClassParams = {String.class, int.class};
Sie entsprechen den Parametern unseres Konstruktors (der nur über Stringund- intParameter verfügt). Wir übergeben sie an die clazz.getConstructor()Methode und erhalten Zugriff auf den gewünschten Konstruktor. Danach müssen wir nur noch die Methode mit den erforderlichen Argumenten aufrufen newInstance()und nicht vergessen, das Objekt explizit in den gewünschten Typ umzuwandeln: Cat.

cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Fluffy", 6);
Jetzt ist unser Objekt erfolgreich erstellt! Konsolenausgabe:

Cat{name='Fluffy', age=6}
Weiter geht's :)

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

Stellen Sie sich vor, Sie verwenden eine Klasse, die von einem anderen Programmierer geschrieben wurde. Darüber hinaus haben Sie keine Möglichkeit, es zu bearbeiten. Zum Beispiel eine vorgefertigte Klassenbibliothek, verpackt in einem JAR. Sie können den Code der Klassen lesen, aber nicht ändern. Angenommen, der Programmierer, der eine der Klassen in dieser Bibliothek erstellt hat (sei es unsere alte CatKlasse), hat in der Nacht vor der Fertigstellung des Entwurfs nicht genug Schlaf bekommen und den Getter und Setter für das ageFeld entfernt. Jetzt ist dieser Kurs zu Ihnen gekommen. CatEs erfüllt alle Ihre Anforderungen, da Sie nur Objekte in Ihrem Programm benötigen . Aber Sie brauchen sie, um ein ageFeld zu haben! Das ist ein Problem: Wir können das Feld nicht erreichen, weil es das hatprivateModifikator und Getter und Setter wurden von dem schlaflosen Entwickler gelöscht, der die Klasse erstellt hat :/ Nun, Reflexion kann uns in dieser Situation helfen! Wir haben Zugriff auf den Code der CatKlasse, sodass wir zumindest herausfinden können, welche Felder sie hat und wie sie heißen. Mit diesen Informationen können wir unser Problem lösen:

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());
   }
}
Wie in den Kommentaren erwähnt, ist alles mit dem nameFeld unkompliziert, da die Klassenentwickler einen Setter bereitgestellt haben. Sie wissen bereits, wie man Objekte aus Standardkonstruktoren erstellt: Wir haben die newInstance()dafür. Aber am zweiten Feld müssen wir noch etwas herumbasteln. Lasst uns herausfinden, was hier los ist :)

Field age = clazz.getDeclaredField("age");
Hier greifen wir mit unserem Class clazzObjekt ageüber die Methode auf das Feld zu getDeclaredField(). Dadurch können wir das Altersfeld als Field ageObjekt erhalten. Dies reicht jedoch nicht aus, da wir privateFeldern nicht einfach Werte zuweisen können. Dazu müssen wir das Feld mit der folgenden setAccessible()Methode zugänglich machen:

age.setAccessible(true);
Sobald wir dies mit einem Feld tun, können wir einen Wert zuweisen:

age.set(cat, 6);
Wie Sie sehen, Field ageverfügt unser Objekt über eine Art Inside-Out-Setter, an den wir einen int-Wert und das Objekt übergeben, dessen Feld zugewiesen werden soll. Wir führen unsere main()Methode aus und sehen:

Cat{name='Fluffy', age=6}
Exzellent! Wir haben es geschafft! :) Mal sehen, was wir sonst noch tun können...

So rufen Sie eine Instanzmethode nach Namen auf.

Lassen Sie uns die Situation im vorherigen Beispiel leicht ändern. Nehmen wir an, der CatKlassenentwickler hat bei den Gettern und Settern keinen Fehler gemacht. In dieser Hinsicht ist alles in Ordnung. Jetzt ist das Problem ein anderes: Es gibt eine Methode, die wir unbedingt brauchen, aber der Entwickler hat sie privat gemacht:

private void sayMeow() {

   System.out.println("Meow!");
}
Das heißt, wenn wir CatObjekte in unserem Programm erstellen, können wir die sayMeow()Methode nicht für sie aufrufen. Wir werden Katzen haben, die nicht miauen? Das ist seltsam :/ Wie würden wir das beheben? Wieder einmal hilft uns die Reflection API! Wir kennen den Namen der Methode, die wir benötigen. Alles andere ist eine Formsache:

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();
   }
}
Hier machen wir weitgehend das Gleiche, was wir beim Zugriff auf ein privates Feld getan haben. Zuerst bekommen wir die Methode, die wir brauchen. Es ist in einem MethodObjekt gekapselt:

Method sayMeow = clazz.getDeclaredMethod("sayMeow");
Mit der getDeclaredMethod()Methode gelangen wir zu privaten Methoden. 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);
Hier sieht unser Methodenaufruf wie ein „Rückruf“ aus: Wir sind es gewohnt, einen Punkt zu verwenden, um ein Objekt auf die gewünschte Methode zu verweisen ( ), cat.sayMeow()aber wenn wir mit Reflektion arbeiten, übergeben wir der Methode das Objekt, das wir aufrufen möchten diese Methode. Was ist auf unserer Konsole?

Meow!
Alles hat funktioniert! :) Jetzt können Sie die enormen Möglichkeiten sehen, die uns der Reflexionsmechanismus von Java bietet. In schwierigen und unerwarteten Situationen (wie in unseren Beispielen mit einer Klasse aus einer geschlossenen Bibliothek) kann es uns wirklich sehr helfen. Aber wie jede Großmacht bringt sie große Verantwortung mit sich. Die Nachteile der Reflexion werden in einem speziellen Abschnitt auf der Oracle-Website beschrieben. Es gibt drei Hauptnachteile:
  1. Die Leistung ist schlechter. Methoden, die mithilfe von Reflektion aufgerufen werden, weisen eine schlechtere Leistung auf als Methoden, die auf normale Weise aufgerufen werden.

  2. Es gibt Sicherheitsbeschränkungen. Mit dem Reflexionsmechanismus können wir das Verhalten eines Programms zur Laufzeit ändern. Aber an Ihrem Arbeitsplatz, wenn Sie an einem echten Projekt arbeiten, können Sie auf Einschränkungen stoßen, die dies nicht zulassen.

  3. Risiko der Offenlegung interner Informationen. Es ist wichtig zu verstehen, dass Reflexion einen direkten Verstoß gegen das Prinzip der Kapselung darstellt: Sie ermöglicht uns den Zugriff auf private Felder, Methoden usw. Ich glaube nicht, dass ich erwähnen muss, dass ein direkter und offensichtlicher Verstoß gegen die Prinzipien von OOP begangen werden sollte Nur in den extremsten Fällen, wenn es aus Gründen, die außerhalb Ihrer Kontrolle liegen, keine andere Möglichkeit gibt, ein Problem zu lösen.

Setzen Sie Reflexion mit Bedacht und nur in Situationen ein, in denen sie nicht vermieden werden kann, und vergessen Sie nicht ihre Mängel. Damit ist unsere Lektion zu Ende. Es ist ziemlich lang geworden, aber du hast heute viel gelernt :)
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION