W3docs

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 level

Come 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 User

Gli 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:

  1. Chiama solo il costruttore senza argomenti. Non è possibile passare argomenti.
  2. 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.

java— editable, runs on the server

Cosa trarre dall'esecuzione:

  • La factory generica build ha creato Widget e Hidden da una Class più un array di tipi di parametri — senza nominare nessun tipo in un'espressione new. 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 il Widget predefinito, dimostrando il moderno sostituto di Class.newInstance(). Preferiscilo sempre: ti permette di scegliere il costruttore e instrada le eccezioni del costruttore attraverso InvocationTargetException invece di far fuoriuscire eccezioni checked non dichiarate.
  • L'istanza Hidden riflessiva non era lo stesso oggetto di Hidden.INSTANCE (same instance? false). setAccessible(true) ha superato direttamente il costruttore private e 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 IllegalArgumentException dal suo corpo, e questa è emersa come InvocationTargetException con la causa reale all'interno — stesso wrapping di Method.invoke. La validazione al momento della costruzione è preservata attraverso la reflection; devi solo estrarre la causa per vederla.
  • Constructor<T> ha restituito un T tipizzato (Widget, Hidden) senza cast, a differenza dell'Object grezzo di Method.invoke. Poiché il costruttore è generico nella classe che costruisce, la factory rimane type-safe al suo confine anche se tutto al suo interno è riflessivo.

Esercitazione

Pratica
Un collega scrive 'MyService s = MyService.class.newInstance();' e il linter segnala 'newInstance()' come deprecato. Qual è il sostituto raccomandato e qual è il motivo pratico principale per cui la vecchia forma era deprecata?
Un collega scrive 'MyService s = MyService.class.newInstance();' e il linter segnala 'newInstance()' come deprecato. Qual è il sostituto raccomandato e qual è il motivo pratico principale per cui la vecchia forma era deprecata?
Was this page helpful?