W3docs

Introduzione alla Reflection in Java

Cos'è la reflection in Java, quando usarla e panoramica del pacchetto java.lang.reflect.

La Reflection è la capacità di un programma di ispezionare e manipolare la propria struttura a runtime: chiedere a una classe quali campi, metodi e costruttori possiede, leggere e scrivere quei campi, invocare quei metodi e creare nuove istanze — il tutto senza nominare i tipi in fase di compilazione. Dove il normale Java è statico (il compilatore conosce ogni tipo che tocchi), la reflection è dinamica (i tipi vengono scoperti da stringhe, configurazioni o da ciò che viene caricato a runtime). Questa parte del libro è una panoramica di java.lang.reflect; questo capitolo introduce il contesto.

Cosa permette di fare la reflection

Il codice normale nomina il tipo con cui lavora:

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

Il codice riflessivo raggiunge lo stesso risultato senza scrivere User o getName come token a tempo di compilazione — sono stringhe risolte a runtime:

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

La seconda forma è molto più verbosa e molto più lenta, e scarta il controllo dei tipi a tempo di compilazione. Non la scriveresti mai per la logica applicativa ordinaria. Vi si ricorre precisamente quando il tipo non è noto fino a runtime.

Quando la reflection vale la pena

La reflection è il motore dei framework, non del codice quotidiano. Utilizzi tipici:

  • Dependency injection (Spring, Guice, CDI): il container legge le annotazioni e le firme dei costruttori, poi istanzia e collega bean di cui non ha mai visto il sorgente.
  • Serializzazione (Jackson, Gson): una libreria JSON scorre i campi di un oggetto per leggerli o popolarli senza che tu scriva codice di mapping per ogni classe.
  • ORM (Hibernate, JPA): mappa le colonne ai campi riflettendo su una classe entità.
  • Test runner (JUnit): trova i metodi annotati con @Test e li invoca.
  • Sistemi di plugin: carica una classe indicata in un file di configurazione e chiama su di essa un metodo di un'interfaccia nota.

Il filo conduttore: un meccanismo generale che opera su tipi utente arbitrari contro cui non è stato compilato. È esattamente il problema che la reflection risolve.

I tipi principali in java.lang.reflect

La reflection parte da un oggetto Class (trattato nel capitolo successivo, Java Class Objects) e si ramifica in una piccola famiglia di classi, ognuna delle quali descrive un tipo di membro:

TipoRappresentaOttenuto da Class tramite
Fieldun campogetField / getDeclaredField(s)
Methodun metodogetMethod / getDeclaredMethod(s)
Constructor<T>un costruttoregetConstructor / getDeclaredConstructor(s)
Parameterun parametro di metodo/costruttoreExecutable.getParameters()
Modifierun helper per il bitset int dei modificatorimetodi statici

Field, Method e Constructor condividono una gerarchia di superclassi comune: Member (l'interfaccia) e AccessibleObject (che porta setAccessible). Questa base condivisa è il motivo per cui "renderlo accessibile" e "leggerne le annotazioni" appaiono identici indipendentemente dal membro che si ha in mano.

La distinzione tra get… e getDeclared…

Quasi ogni metodo di ricerca esiste in due varianti, e la distinzione è importante in ogni capitolo successivo:

  • getField / getMethod / getConstructor — restituisce i membri pubblici, inclusi quelli ereditati da superclassi e interfacce.
  • getDeclaredField / getDeclaredMethod / getDeclaredConstructor — restituisce i membri di qualsiasi livello di accesso (private, protected, package), ma solo quelli dichiarati su questa classe esatta — niente di ereditato.

Quindi getMethods() vede un metodo pubblico ereditato da un genitore ma non un helper private sulla classe stessa; getDeclaredMethods() vede l'helper private ma non il metodo pubblico ereditato. Per raggiungere un membro privato ereditato si risale con getSuperclass() chiamando getDeclared… a ogni livello.

Il costo: velocità, sicurezza ed encapsulation

La reflection è potente, e ogni potere ha un prezzo.

  • Prestazioni. Le chiamate riflessive sono più lente delle chiamate dirette — ricerche di metodi/campi, controlli di accesso e boxing degli argomenti aggiungono tutti overhead. Il JIT ottimizza bene le chiamate riflessive frequenti, ma la reflection in un ciclo stretto è un segnale d'allarme. Metti in cache gli oggetti Method/Field; non ricercarli a ogni chiamata.
  • Nessuna sicurezza a tempo di compilazione. Un errore di battitura nel nome di un metodo viene compilato senza problemi e fallisce a runtime con NoSuchMethodException. Gli strumenti di refactoring rinominano getName ovunque — tranne all'interno della stringa "getName".
  • Violazione dell'encapsulation. setAccessible(true) permette di leggere e scrivere lo stato private. È così che i serializzatori popolano i campi senza setter, ma questo ti accoppia a dettagli interni che l'autore della classe non ha mai promesso di mantenere stabili.
  • Restrizioni del sistema di moduli. Da Java 9, il sistema di moduli può negare l'accesso riflessivo ai pacchetti non esportati. Chiamare setAccessible(true) attraverso un confine di modulo che non ha opens il pacchetto lancia InaccessibleObjectException.

Un esempio pratico: un piccolo dump generico di oggetti

Per rendere concreto l'ambito, ecco una routine riflessiva che stampa i campi e i valori di qualsiasi oggetto — il tipo di cosa che fa un debugger o una libreria di logging. Non nomina alcun tipo applicativo; funziona su qualunque cosa le venga passata.

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • Il metodo dump non ha nominato alcun tipo concreto eppure ha stampato sia Point che User. Il suo unico contratto è "passami un Object"; tutto sulla struttura — nomi dei campi, tipi, valori — proveniva da getClass() a runtime. Questa è la mossa fondamentale della reflection: una routine, input arbitrari.
  • getDeclaredFields() ha restituito tutti i campi inclusi quelli private, ma per leggerli era necessario chiamare prima setAccessible(true). Senza quella chiamata, f.get(obj) su un campo privato lancia IllegalAccessException. La ricerca e l'accesso sono due controlli separati.
  • Modifier.toString(f.getModifiers()) ha trasformato il bitset grezzo dei modificatori in testo leggibile come private final. I modificatori sono memorizzati come un int di bit di flag; l'helper Modifier li decodifica così non devi testare i bit manualmente.
  • Il terzo oggetto è stato costruito senza alcun new User(...) nel sorgente — Class.forName("…$User") ha risolto la classe annidata da una stringa (nota il separatore $ per i tipi annidati), e getDeclaredConstructor(...).newInstance(...) lo ha costruito. Questo è il pattern di caricamento dei plugin in miniatura: un nome in entrata, un oggetto in uscita.
  • Leggere il valore del campo (f.get(obj)) e leggere i metadati del campo (f.getName(), f.getModifiers()) sono operazioni indipendenti. I metadati non richiedono né istanza né accessibilità; i valori richiedono l'oggetto e, per i campi privati, il flag di accessibilità.

Come è organizzato il resto di questa parte

Ogni capitolo rimanente approfondisce un aspetto specifico:

  • Class objects — i tre modi per ottenere un Class<T> e cosa rivela.
  • Fields — ispezione, lettura e scrittura dei campi (inclusi private e final).
  • Methods — trovare e invocare metodi, risoluzione degli overload, valori di ritorno.
  • Constructors — costruire istanze in modo riflessivo, inclusi i costruttori privati.
  • Annotations — leggere i metadati allegati con le annotazioni, a runtime.
  • Dynamic proxies — sintetizzare intere implementazioni di interfacce a runtime.

Per tutto il corso, tieni presente il compromesso: la reflection è lo strumento giusto quando il tipo non è genuinamente noto fino a runtime, e quello sbagliato quando lo è. Il capitolo successivo parte dalla radice di tutto — l'oggetto Class.

Pratica

Pratica
Una libreria di logging deve stampare i valori dei campi di qualsiasi oggetto passato al suo metodo 'log(Object o)', inclusi oggetti le cui classi non ha mai compilato e i cui campi sono 'private'. Qual è la combinazione minimale corretta?
Una libreria di logging deve stampare i valori dei campi di qualsiasi oggetto passato al suo metodo 'log(Object o)', inclusi oggetti le cui classi non ha mai compilato e i cui campi sono 'private'. Qual è la combinazione minimale corretta?
Was this page helpful?