Java Reflection: Chiamare i Costruttori
Istanzia classi Java in modo riflessivo con Constructor.newInstance e Class.getDeclaredConstructor.
Creare un oggetto senza scrivere new è il trucco riflessivo alla base di ogni container di dependency injection, deserializzatore e plugin loader: hai una Class e hai bisogno di un'istanza. L'oggetto Constructor<T> rappresenta un costruttore e crea istanze con newInstance(args...). Questo capitolo tratta la ricerca dei costruttori, la loro chiamata con argomenti, l'accesso ai costruttori private e il motivo per cui la vecchia scorciatoia Class.newInstance() è deprecata.
Se sei nuovo alla reflection, inizia con l'introduzione alla reflection, poi torna qui. I meccanismi descritti di seguito rispecchiano quelli visti per la chiamata di metodi e la lettura di campi in modo riflessivo.
Trovare i costruttori
I costruttori vengono cercati solo per tipi di parametri — non esiste un nome, poiché tutti i costruttori condividono il nome della classe:
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 levelCome ovunque nella reflection, i tipi di parametri devono corrispondere esattamente (int.class, non Integer.class), e getConstructor vede solo quelli public mentre getDeclaredConstructor vede anche private/protected/package. Nota che Constructor<T> è generico nella classe che costruisce, quindi newInstance restituisce un T tipizzato (a differenza di Method.invoke che restituisce un Object grezzo).
Costruire istanze con newInstance
Constructor<User> ctor = User.class.getDeclaredConstructor(String.class, int.class);
User u = ctor.newInstance("ada", 36); // returns a typed UserGli argomenti funzionano esattamente come con Method.invoke: un varargs Object[], con i primitivi in autoboxing. La differenza è che non c'è un oggetto target — un costruttore crea il target. Le eccezioni lanciate dal corpo del costruttore vengono racchiuse in InvocationTargetException, esattamente come con i metodi; estrai la causa con getCause().
Accedere ai costruttori privati
I singleton, le classi di utilità e i builder spesso nascondono il proprio costruttore. La reflection supera questo ostacolo con 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!Questo è genuinamente potente e genuinamente pericoloso: viola la garanzia del singleton, il contratto "nessuna istanza" di una classe di utilità e qualsiasi invariante che il costruttore stava proteggendo. (Un singleton enum è l'unica forma che la reflection non può istanziare — Constructor.newInstance rifiuta esplicitamente i tipi enum con IllegalArgumentException, il che è in parte il motivo per cui il "singleton enum" è il pattern raccomandato.)
Perché Class.newInstance() è deprecato
Nel vecchio codice troverai la scorciatoia clazz.newInstance():
User u = User.class.newInstance(); // DEPRECATED since Java 9È deprecato per due motivi concreti:
- Chiama solo il costruttore senza argomenti. Non è possibile passare argomenti.
- Gestisce male le eccezioni. Se il costruttore senza argomenti lancia un'eccezione checked,
Class.newInstance()la propaga senza dichiararla — vanificando l'analisi delle eccezioni checked del compilatore.
Il sostituto è sempre:
User u = User.class.getDeclaredConstructor().newInstance();Questa è una riga più lunga, chiama un costruttore scelto esplicitamente e racchiude le eccezioni del costruttore in InvocationTargetException così nulla sfugge non dichiarato. Usala come idioma standard anche per il caso senza argomenti.
Un esempio completo: una piccola factory riflessiva
Il programma costruisce oggetti in tre modi: un costruttore pubblico multi-argomento, un costruttore private raggiunto tramite setAccessible, e il moderno idioma senza argomenti — poi mostra un costruttore che lancia eccezioni che vengono racchiuse, e la firma della scorciatoia deprecata per confronto.
Cosa trarre dall'esecuzione:
- La factory generica
buildha creatoWidgeteHiddenda unaClasspiù un array di tipi di parametri — senza nominare nessun tipo in un'espressionenew. Quella firma,<T> T build(Class<T>, Class<?>[], Object...), è essenzialmente come appare il nucleo di istanziazione di un container DI: passagli un tipo e degli argomenti, ottieni un'istanza. getDeclaredConstructor().newInstance()ha prodotto ilWidgetpredefinito, dimostrando il moderno sostituto diClass.newInstance(). Preferiscilo sempre: ti permette di scegliere il costruttore e instrada le eccezioni del costruttore attraversoInvocationTargetExceptioninvece di far fuoriuscire eccezioni checked non dichiarate.- L'istanza
Hiddenriflessiva non era lo stesso oggetto diHidden.INSTANCE(same instance? false).setAccessible(true)ha superato direttamente il costruttoreprivatee ha coniato una seconda istanza — prova concreta che la reflection può rompere la garanzia fondamentale di un singleton. I singleton difensivi lanciano eccezioni dal costruttore se un'istanza esiste già; gli enum sono immuni per costruzione. - Il costruttore che ha rifiutato una dimensione negativa ha lanciato
IllegalArgumentExceptiondal suo corpo, e questa è emersa comeInvocationTargetExceptioncon la causa reale all'interno — stesso wrapping diMethod.invoke. La validazione al momento della costruzione è preservata attraverso la reflection; devi solo estrarre la causa per vederla. Constructor<T>ha restituito unTtipizzato (Widget,Hidden) senza cast, a differenza dell'Objectgrezzo diMethod.invoke. Poiché il costruttore è generico nella classe che costruisce, la factory rimane type-safe al suo confine anche se tutto al suo interno è riflessivo.