1. Creazione di oggetti tramite reflection
Talvolta dobbiamo creare oggetti senza conoscerne il tipo in fase di compilazione. Per esempio, il nome della classe arriva dalla configurazione oppure stiamo scrivendo un framework generico (serializer, contenitore DI). Nel codice normale scriveremmo:
User user = new User("Ivan", 25);
Ma cosa succede se non sappiamo che la classe si chiama User e non conosciamo i parametri del suo costruttore? Qui ci aiuta la reflection.
Metodo obsoleto: Class.newInstance()
In passato in Java esisteva il metodo Class.newInstance(), che crea un oggetto tramite il costruttore pubblico senza parametri:
Class<?> clazz = Class.forName("com.example.User");
Object obj = clazz.newInstance();
IMPORTANTE: Questo metodo è contrassegnato come deprecato da Java 9 e non è raccomandato. Non consente di scegliere il costruttore, nasconde le cause degli errori e richiede unicamente un costruttore public senza parametri.
Metodo moderno: tramite costruttori
Nel codice reale non sempre esiste un costruttore public senza parametri. L’approccio attuale è ottenere il costruttore desiderato tramite getConstructor(...) o getDeclaredConstructor(...), quindi chiamare su di esso newInstance(...).
Esempio: creazione di un oggetto con parametri
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
import java.lang.reflect.Constructor;
Class<?> clazz = Class.forName("User");
// Otteniamo il costruttore con i parametri String, int
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
// Creiamo l'oggetto passando i parametri
Object user = constructor.newInstance("Ivan", 25);
System.out.println(user); // toString, se definito
Cosa sta succedendo qui?
- Otteniamo il costruttore desiderato in base alla firma.
- Chiamiamo su di esso newInstance(...), passando gli argomenti.
- Otteniamo un oggetto di tipo Object (si può effettuare il cast a User, se il tipo è noto).
E se il costruttore è privato?
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true); // Magia! Ora possiamo chiamare il costruttore privato.
Object user = constructor.newInstance("Ivan", 25);
ATTENZIONE: non abusarne — si viola l’incapsulamento. Nelle applicazioni modulari (Java 9+) si applicano anche restrizioni di accesso.
Tabella di confronto dei metodi di creazione degli oggetti
| Metodo | Velocità | Sicurezza dei tipi | Quando usarlo |
|---|---|---|---|
|
La più veloce | Completa | Sempre, quando possibile |
|
Lenta | Persa | Framework e plugin |
|
Media | Parziale | Librerie ad alte prestazioni |
| Factory | Veloce | Completa | Creazione flessibile di oggetti |
2. Invocare metodi tramite reflection
Puoi invocare qualsiasi metodo di un oggetto, anche se non ne conosci il nome in fase di compilazione o se è private.
Recupero del metodo
import java.lang.reflect.Method;
Class<?> clazz = user.getClass();
// Otteniamo un metodo public per nome e tipi dei parametri
Method method = clazz.getMethod("getName"); // Senza parametri
Object result = method.invoke(user); // Chiamata del metodo senza parametri
System.out.println(result); // Stampa il nome dell'utente
Invocazione di un metodo con parametri
Method setName = clazz.getMethod("setName", String.class);
setName.invoke(user, "Petr"); // Impostiamo il nuovo nome
Invocazione di un metodo privato
Method secret = clazz.getDeclaredMethod("secretMethod", int.class);
secret.setAccessible(true); // Rimuoviamo la protezione
Object secretResult = secret.invoke(user, 123);
Importante: Tutti i parametri vengono passati come array di oggetti (varargs). Se il metodo restituisce qualcosa, il risultato arriva come Object.
3. Accesso ai campi tramite reflection
A volte è necessario leggere o modificare il valore di un campo, anche se è private (per esempio durante la serializzazione o nei test).
Recupero del campo
import java.lang.reflect.Field;
Class<?> clazz = user.getClass();
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true); // Se il campo non è public
// Lettura del valore
Object age = ageField.get(user);
System.out.println("Età: " + age);
// Modifica del valore
ageField.set(user, 42);
System.out.println("Nuova età: " + ageField.get(user));
Lavorare con i campi statici
Se il campo è statico, passa a get/set null al posto dell’oggetto:
Field staticField = clazz.getDeclaredField("counter");
staticField.setAccessible(true);
staticField.set(null, 100); // Per i campi static non serve un'istanza
4. Limitazioni ed eccezioni
Lavorare con la reflection può essere accompagnato da molte eccezioni checked. Le più comuni:
- ClassNotFoundException — classe con quel nome non trovata.
- NoSuchMethodException — non esiste un costruttore o un metodo con tale firma.
- NoSuchFieldException — campo non trovato.
- IllegalAccessException — nessun accesso al membro (per esempio, metodo privato senza setAccessible(true)).
- InstantiationException — la classe è astratta o è un’interfaccia; impossibile creare un’istanza.
- InvocationTargetException — errore all’interno del costruttore o del metodo invocato.
Esempio di gestione:
try {
// ... il tuo codice di reflection ...
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
Se i dettagli non sono importanti, è più comodo catturare la superclasse comune ReflectiveOperationException (Java 7+).
5. Pratica: mini-programma “Istanziatore dinamico”
Classe di esempio per gli esperimenti
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
private void sayHello() {
System.out.println("Ciao, mi chiamo " + name + ", ho " + age + " anni.");
}
}
Creazione e manipolazione dinamica
import java.lang.reflect.*;
public class ReflectionDemo {
public static void main(String[] args) {
try {
// 1. Otteniamo l'oggetto Class per nome
Class<?> clazz = Class.forName("Person");
// 2. Otteniamo il costruttore e creiamo l'oggetto
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object person = constructor.newInstance("Alisa", 30);
// 3. Chiamiamo il metodo privato sayHello
Method sayHello = clazz.getDeclaredMethod("sayHello");
sayHello.setAccessible(true);
sayHello.invoke(person); // Ciao, mi chiamo Alisa, ho 30 anni.
// 4. Modifichiamo il campo privato name
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(person, "Bob");
// 5. Chiamiamo di nuovo sayHello
sayHello.invoke(person); // Ciao, mi chiamo Bob, ho 30 anni.
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
}
Nota:
- Tutto avviene senza conoscenza diretta del tipo Person nel codice.
- È possibile modificare campi privati e chiamare metodi privati (se consentito dalla politica di sicurezza della JVM e dalla configurazione modulare).
6. Come si collega ad applicazioni reali?
La reflection è alla base di molte librerie e framework popolari:
- JUnit: ricerca di metodi con annotazione @Test, creazione delle istanze dei test e invocazioni dei metodi.
- Spring: DI, creazione di bean, autowiring.
- Jackson, Gson: serializzazione/deserializzazione dei campi degli oggetti.
- Hibernate: accesso ai campi delle entità, proxy e lazy loading.
7. Schema visivo: come si crea un oggetto tramite reflection
flowchart TB
A["Nome della classe (String)"] --> B["Class.forName"]
B --> C["Class<?>"]
C --> D["getConstructor(...)"]
D --> E["Constructor<?>"]
E --> F["newInstance(...)"]
F --> G["Object"]
8. Errori tipici nel lavoro con la reflection
Errore n. 1: Costruttore sbagliato. Richiedi un costruttore con una firma errata, ad esempio getConstructor(String.class) quando esiste solo un costruttore con due parametri — otterrai NoSuchMethodException. Verifica sempre le firme!
Errore n. 2: Mancanza di accesso. Chiamare un costruttore/metodo privato senza setAccessible(true) porta a IllegalAccessException. E ricorda: in Java 9+ il sistema modulare può imporre ulteriori restrizioni.
Errore n. 3: Problemi di tipi. Tutti i parametri e i valori di ritorno tramite reflection sono Object. Un cast errato — ClassCastException.
Errore n. 4: Eccezioni non gestite. La reflection lancia molte eccezioni checked. Gestiscile, ad esempio con la comune ReflectiveOperationException, altrimenti il codice non compilerà.
Errore n. 5: Violazione dell’incapsulamento. L’uso sconsiderato di setAccessible(true) — “accesso al frigorifero di qualcun altro”. Usalo solo in caso di reale necessità e considera i requisiti di sicurezza.
GO TO FULL VERSION