Java Reflection: Konstruktoren aufrufen
Java-Klassen per Reflection instanziieren mit Constructor.newInstance und Class.getDeclaredConstructor.
Ein Objekt zu erzeugen, ohne new zu schreiben, ist der Reflection-Trick hinter jedem Dependency-Injection-Container, Deserialisierer und Plugin-Loader: Man hat eine Class und braucht eine Instanz. Das Constructor<T>-Objekt repräsentiert einen Konstruktor und erstellt Instanzen mit newInstance(args...). Dieses Kapitel behandelt das Finden von Konstruktoren, den Aufruf mit Argumenten, den Zugriff auf private-Konstruktoren und warum der alte Class.newInstance()-Shortcut als veraltet markiert ist.
Wenn du neu bei Reflection bist, beginne mit der Reflection-Einführung und kehre dann hierher zurück. Die unten beschriebenen Mechanismen spiegeln das wider, was du für das Aufrufen von Methoden und das Lesen von Feldern per Reflection gesehen hast.
Konstruktoren finden
Konstruktoren werden nur anhand der Parametertypen gesucht — es gibt keinen Namen, da alle Konstruktoren den Namen der Klasse teilen:
Class<User> c = User.class;
Constructor<User> noArg = c.getDeclaredConstructor(); // ()
Constructor<User> twoArg = c.getDeclaredConstructor(String.class, int.class); // (String, int)
Constructor<?>[] pub = c.getConstructors(); // public only
Constructor<?>[] all = c.getDeclaredConstructors(); // any access levelWie überall in Reflection müssen Parametertypen exakt übereinstimmen (int.class, nicht Integer.class), und getConstructor sieht nur public-Konstruktoren, während getDeclaredConstructor auch private-, protected- und Package-Konstruktoren sieht. Beachte: Constructor<T> ist generisch für die Klasse, die es erzeugt, sodass newInstance ein typisiertes T zurückgibt (anders als Method.invoke's rohes Object).
Instanzen mit newInstance erstellen
Constructor<User> ctor = User.class.getDeclaredConstructor(String.class, int.class);
User u = ctor.newInstance("ada", 36); // returns a typed UserArgumente funktionieren genauso wie bei Method.invoke: ein Varargs-Object[], Primitives werden automatisch geboxt. Der Unterschied ist, dass es kein Zielobjekt gibt — ein Konstruktor erstellt das Ziel. Vom Konstruktorrumpf geworfene Ausnahmen werden in InvocationTargetException eingewickelt, genau wie bei Methoden; entpacke sie mit getCause().
Zugriff auf private Konstruktoren
Singletons, Utility-Klassen und Builder verstecken oft ihren Konstruktor. Reflection geht einfach daran vorbei mit setAccessible(true):
Constructor<Singleton> ctor = Singleton.class.getDeclaredConstructor();
ctor.setAccessible(true); // bypass the private modifier
Singleton fresh = ctor.newInstance(); // a SECOND instance — breaks the singleton!Das ist wirklich mächtig und wirklich gefährlich: Es hebt die Singleton-Garantie auf, den "keine Instanzen"-Vertrag einer Utility-Klasse und jede Invariante, die der Konstruktor schützen sollte. (Ein enum-Singleton ist die einzige Form, die Reflection nicht instanziieren kann — Constructor.newInstance lehnt enum-Typen explizit mit IllegalArgumentException ab, was ein Grund dafür ist, warum "enum-Singleton" das empfohlene Muster ist.)
Warum Class.newInstance() veraltet ist
Du wirst in altem Code den Shortcut clazz.newInstance() sehen:
User u = User.class.newInstance(); // DEPRECATED since Java 9Er ist aus zwei echten Gründen veraltet:
- Er ruft nur den No-Arg-Konstruktor auf. Keine Möglichkeit, Argumente zu übergeben.
- Er behandelt Ausnahmen falsch. Wenn der No-Arg-Konstruktor eine geprüfte Ausnahme wirft, propagiert
Class.newInstance()sie ohne sie zu deklarieren — was die geprüfte-Ausnahme-Analyse des Compilers untergräbt.
Der Ersatz lautet immer:
User u = User.class.getDeclaredConstructor().newInstance();Dies ist eine Zeile länger, ruft einen explizit gewählten Konstruktor auf und wickelt Konstruktorausnahmen in InvocationTargetException ein, sodass nichts undeklariert entkommen kann. Verwende es als Standard-Idiom auch für den No-Arg-Fall.
Ein durchgearbeitetes Beispiel: eine kleine reflektive Factory
Das Programm erstellt Objekte auf drei Arten: einen öffentlichen Multi-Arg-Konstruktor, einen private-Konstruktor, der via setAccessible erreichbar ist, und das moderne No-Arg-Idiom — dann zeigt es, wie ein werfender Konstruktor eingewickelt wird, und die veraltete Signatur zum Vergleich.
Was aus dem Lauf mitzunehmen ist:
- Die generische
build-Factory hatWidgetundHiddenaus einerClassplus einem Parametertyp-Array erstellt — ohne einen der Typen in einemnew-Ausdruck zu nennen. Diese Signatur,<T> T build(Class<T>, Class<?>[], Object...), ist im Wesentlichen das, wie ein DI-Container-Instanziierungskern aussieht: übergib einen Typ und Argumente, erhalte eine Instanz zurück. getDeclaredConstructor().newInstance()hat den Standard-Widgeterstellt und damit den modernen Ersatz fürClass.newInstance()demonstriert. Bevorzuge ihn immer: Er ermöglicht die Wahl des Konstruktors und leitet Konstruktorausnahmen durchInvocationTargetException, anstatt undeklarierte geprüfte Ausnahmen zu leaken.- Die reflektive
Hidden-Instanz war nicht dasselbe Objekt wieHidden.INSTANCE(same instance? false).setAccessible(true)ging direkt amprivate-Konstruktor vorbei und prägte eine zweite Instanz — konkreter Beweis, dass Reflection die Kerngarantie eines Singletons brechen kann. Defensiv-Singletons werfen im Konstruktor, wenn bereits eine Instanz existiert; Enums sind durch ihre Konstruktion immun. - Der Konstruktor, der eine negative Größe ablehnte, warf
IllegalArgumentExceptionaus seinem Rumpf, und das tauchte alsInvocationTargetExceptionmit dem echten Grund darin auf — identische Einwicklung wie beiMethod.invoke. Validierung zur Konstruktionszeit bleibt durch Reflection erhalten; man muss nur entpacken, um sie zu sehen. Constructor<T>gab ein typisiertesT(Widget,Hidden) ohne Cast zurück, anders alsMethod.invoke's rohesObject. Da der Konstruktor generisch in der Klasse ist, die er erstellt, bleibt die Factory an ihrer Grenze typsicher, auch wenn alles innen reflektiv ist.