W3docs

Einführung in Java Reflection

Was Reflection in Java ist, wann man es einsetzt und ein Überblick über das java.lang.reflect-Paket.

Reflection ist die Fähigkeit eines Programms, seine eigene Struktur zur Laufzeit zu untersuchen und zu verändern: eine Klasse nach ihren Feldern, Methoden und Konstruktoren zu befragen, diese Felder zu lesen und zu schreiben, Methoden aufzurufen und neue Instanzen zu erstellen — alles ohne die Typen zur Compilezeit zu benennen. Während gewöhnliches Java statisch ist (der Compiler kennt jeden verwendeten Typ), ist Reflection dynamisch (Typen werden aus Strings, Konfigurationsdateien oder zur Laufzeit geladenen Klassen ermittelt). Dieser Teil des Buchs ist eine Tour durch java.lang.reflect; dieses Kapitel legt den Grundstein.

Was Reflection ermöglicht

Normaler Code benennt den Typ, mit dem er arbeitet:

User u = new User("ada");
String name = u.getName();

Reflektiver Code erreicht dasselbe Ergebnis, ohne User oder getName als Compile-Zeit-Token zu schreiben — sie sind Strings, die zur Laufzeit aufgelöst werden:

Class<?> cls = Class.forName("com.example.User");
Object u = cls.getDeclaredConstructor(String.class).newInstance("ada");
Object name = cls.getMethod("getName").invoke(u);

Die zweite Form ist weitaus ausführlicher und langsamer, und sie verzichtet auf die Typsicherheit zur Compilezeit. Man würde sie niemals für gewöhnliche Anwendungslogik verwenden. Man greift genau dann darauf zurück, wenn der Typ erst zur Laufzeit bekannt ist.

Wann Reflection sinnvoll ist

Reflection ist das Herzstück von Frameworks, nicht von alltäglichem Code. Typische Einsatzbereiche:

  • Dependency Injection (Spring, Guice, CDI): Der Container liest Annotationen und Konstruktorsignaturen, instanziiert dann Beans und verdrahtet sie, ohne je deren Quellcode gesehen zu haben.
  • Serialisierung (Jackson, Gson): Eine JSON-Bibliothek durchläuft die Felder eines Objekts, um sie zu lesen oder zu befüllen, ohne dass man für jede Klasse Mapping-Code schreiben muss.
  • ORMs (Hibernate, JPA): Spalten werden Feldern zugeordnet, indem die Entity-Klasse per Reflection untersucht wird.
  • Test-Runner (JUnit): Methoden mit @Test werden gefunden und aufgerufen.
  • Plugin-Systeme: Eine in einer Konfigurationsdatei genannte Klasse wird geladen, und eine bekannte Interface-Methode wird auf ihr aufgerufen.

Der gemeinsame Nenner: ein allgemeiner Mechanismus, der über beliebige Benutzertypen operiert, gegen die er nicht kompiliert wurde. Genau das ist das Problem, das Reflection löst.

Die zentralen Typen in java.lang.reflect

Reflection beginnt mit einem Class-Objekt (behandelt im nächsten Kapitel, Java Class-Objekte) und verzweigt sich in eine kleine Familie von Klassen, die jeweils eine Art von Member beschreiben:

TypRepräsentiertAus Class ermittelt via
Fieldein FeldgetField / getDeclaredField(s)
Methodeine MethodegetMethod / getDeclaredMethod(s)
Constructor<T>einen KonstruktorgetConstructor / getDeclaredConstructor(s)
Parametereinen Methoden-/KonstruktorparameterExecutable.getParameters()
Modifierein Hilfsobjekt für den int-Modifier-Bitsetstatische Methoden

Field, Method und Constructor teilen eine gemeinsame Superklassen-Hierarchie: Member (das Interface) und AccessibleObject (das setAccessible bereitstellt). Diese gemeinsame Basis ist der Grund, warum "zugänglich machen" und "Annotationen lesen" identisch aussehen, egal welches Member man hält.

Die Unterscheidung zwischen get… und getDeclared…

Fast jede Suchmethode existiert in zwei Varianten, und der Unterschied ist in jedem späteren Kapitel relevant:

  • getField / getMethod / getConstructor — gibt public-Member zurück, einschließlich vererbter von Superklassen und Interfaces.
  • getDeclaredField / getDeclaredMethod / getDeclaredConstructor — gibt Member beliebiger Zugriffsebene zurück (private, protected, package), aber nur jene, die in dieser exakten Klasse deklariert sind — keine geerbten.

getMethods() sieht also eine von einer Elternklasse geerbte public-Methode, aber keine private-Hilfsmethode der Klasse selbst; getDeclaredMethods() sieht die private-Hilfsmethode, aber nicht die geerbte public-Methode. Um auf ein privates geerbtes Member zuzugreifen, muss man mit getSuperclass() nach oben gehen und getDeclared… auf jeder Ebene aufrufen.

Die Kosten: Geschwindigkeit, Sicherheit und Kapselung

Reflection ist mächtig, und jede Macht hat ihren Preis.

  • Performance. Reflektive Aufrufe sind langsamer als direkte Aufrufe — Methoden-/Feldsuchen, Zugriffsüberprüfungen und Argument-Boxing erzeugen Overhead. Der JIT optimiert häufig verwendete reflektive Aufrufe gut, aber Reflection in einer engen Schleife ist ein Warnsignal. Man sollte die Method-/Field-Objekte cachen und sie niemals pro Aufruf neu suchen.
  • Keine Compilezeit-Sicherheit. Ein Tippfehler in einem Methodennamen kompiliert einwandfrei und schlägt erst zur Laufzeit mit NoSuchMethodException fehl. Refactoring-Tools benennen getName überall um — außer innerhalb des Strings "getName".
  • Kapselung wird durchbrochen. setAccessible(true) erlaubt das Lesen und Schreiben von private-Zustand. So befüllen Serializer Felder ohne Setter, aber man koppelt sich damit an interne Details, deren Stabilität der Autor der Klasse nie versprochen hat.
  • Modul-Einschränkungen. Seit Java 9 kann das Modulsystem reflektiven Zugriff auf nicht exportierte Pakete verweigern. Der Aufruf von setAccessible(true) über eine Modulgrenze hinweg, die das Paket nicht per opens freigibt, wirft InaccessibleObjectException.

Ein ausgearbeitetes Beispiel: ein kleiner generischer Objekt-Dumper

Um den Anwendungsbereich konkret zu machen, hier eine einzige reflektive Routine, die die Felder und deren Werte beliebiger Objekte ausgibt — die Art von Funktionalität, die ein Debugger oder eine Logging-Bibliothek einsetzt. Sie benennt keinen Anwendungstyp; sie funktioniert mit allem, was übergeben wird.

java— editable, runs on the server

Was man aus der Ausführung mitnehmen sollte:

  • Die dump-Methode hat keinen konkreten Typ benannt und dennoch sowohl Point als auch User ausgegeben. Ihr einziger Vertrag lautet: "Gib mir ein Object"; alles über die Struktur — Feldnamen, Typen, Werte — kam zur Laufzeit von getClass(). Das ist der grundlegende Zug der Reflection: eine Routine, beliebige Eingaben.
  • getDeclaredFields() hat alle Felder zurückgegeben, einschließlich der private-Felder, aber zum Lesen war vorher setAccessible(true) erforderlich. Ohne diesen Aufruf wirft f.get(obj) bei einem privaten Feld IllegalAccessException. Die Suche und der Zugriff sind zwei separate Hürden.
  • Modifier.toString(f.getModifiers()) hat den rohen Modifier-Bitset in lesbaren Text wie private final umgewandelt. Modifier werden als int von Flag-Bits gespeichert; der Modifier-Helfer dekodiert sie, sodass man die Bits nicht manuell prüfen muss.
  • Das dritte Objekt wurde ohne ein einziges new User(...) im Quellcode erstellt — Class.forName("…$User") hat die verschachtelte Klasse aus einem String aufgelöst (beachte das $-Trennzeichen für verschachtelte Typen), und getDeclaredConstructor(...).newInstance(...) hat sie instanziiert. Das ist das Plugin-Lade-Muster im Miniaturformat: ein Name rein, ein Objekt raus.
  • Das Lesen des Feld-Werts (f.get(obj)) und das Lesen der Feld-Metadaten (f.getName(), f.getModifiers()) sind unabhängig voneinander. Metadaten benötigen keine Instanz und keine Zugänglichkeit; Werte benötigen das Objekt und, bei privaten Feldern, das Zugänglichkeits-Flag.

Wie der Rest dieses Teils aufgebaut ist

Jedes weitere Kapitel beleuchtet eine Ecke im Detail:

  • Class-Objekte — die drei Wege, ein Class<T> zu erhalten und was es verrät.
  • Felder — Felder untersuchen, lesen und schreiben (einschließlich private und final).
  • Methoden — Methoden finden und aufrufen, Überladungsauflösung, Rückgabewerte.
  • Konstruktoren — Instanzen per Reflection erstellen, einschließlich privater Konstruktoren.
  • Annotationen — die Metadaten lesen, die mit Annotationen hinzugefügt wurden, zur Laufzeit.
  • Dynamische Proxys — vollständige Interface-Implementierungen zur Laufzeit synthetisieren.

Dabei sollte man den Trade-off immer im Blick behalten: Reflection ist das richtige Werkzeug, wenn der Typ zur Laufzeit wirklich nicht bekannt ist, und das falsche Werkzeug, wenn er es ist. Das nächste Kapitel beginnt an der Wurzel von allem — dem Class-Objekt.

Übungen

Übung
Eine Logging-Bibliothek soll die Feldwerte beliebiger Objekte ausgeben, die an ihre Methode 'log(Object o)' übergeben werden, einschließlich Objekte deren Klassen nie gegen die Bibliothek kompiliert wurden und deren Felder 'private' sind. Welche Kombination ist der minimal korrekte Ansatz?
Eine Logging-Bibliothek soll die Feldwerte beliebiger Objekte ausgeben, die an ihre Methode 'log(Object o)' übergeben werden, einschließlich Objekte deren Klassen nie gegen die Bibliothek kompiliert wurden und deren Felder 'private' sind. Welche Kombination ist der minimal korrekte Ansatz?
Was this page helpful?