1. Che cos’è la reflection
La reflection in Java è un meccanismo che permette a un programma di ispezionare e persino modificare la propria struttura durante l’esecuzione. È come se potessi guardare dentro te stesso e chiederti: «Quali campi e metodi ho? Quale costruttore? Che classe sono, in generale?» — e perfino invocare un metodo privato su te stesso.
Definizione formale:
La reflection è un’API che consente di ottenere informazioni su classi, interfacce, campi, metodi e costruttori durante l’esecuzione del programma, nonché di lavorare con essi.
Perché serve la reflection?
- Framework: Spring, Hibernate, JUnit usano la reflection per la «magia»: creazione automatica di oggetti, dependency injection, invocazione di metodi per nome e tramite annotazioni.
- Serializzazione: Trasformare oggetti in JSON/XML e viceversa — è necessario conoscere i campi e saperci accedere.
- Testing: Ricerca e invocazione automatica di metodi annotati (per esempio, @Test).
- Caricamento dinamico delle classi: Caricare una classe per nome a runtime (plugin, driver).
- IDE e analisi del codice: Completamento automatico, ispezioni, refactoring.
Esempio concreto
public class Person {
private String name;
private int age;
public void sayHello() {
System.out.println("Ciao, mi chiamo " + name);
}
}
Con la reflection possiamo, durante l’esecuzione, scoprire che la classe Person ha i campi name e age, nonché il metodo sayHello. Inoltre, si può modificare il valore di un campo privato o invocare un metodo per nome.
Un po’ di umorismo
Si può dire che la reflection è come in «Matrix»: ti rendi conto che tutto il tuo codice sono dati che possono essere ispezionati e modificati al volo. Solo, non dimenticare la pillola rossa della sicurezza!
2. La classe Class: il cuore della reflection
In Java ogni oggetto e ogni tipo a runtime è associato a un oggetto speciale di tipo Class. Questo oggetto è la «metainformazione» del tipo. La classe java.lang.Class<T> è il punto d’ingresso centrale nel mondo della reflection.
Come ottenere un oggetto Class?
Ci sono diversi modi:
- Tramite .class
Il modo più diretto e sicuro, se la classe è nota in fase di compilazione:Class<Person> personClass = Person.class; - Da un oggetto: getClass()
Se hai già un’istanza:
Qui clazz è un oggetto Class che descrive la classe reale dell’oggetto p.Person p = new Person(); Class<?> clazz = p.getClass(); - Da una stringa: Class.forName()
Se il nome della classe è noto solo a runtime:
Utile per caricare plugin, driver e altre componenti dinamiche.Class<?> clazz = Class.forName("com.example.Person");
Esempio: ottenere il nome della classe
Person p = new Person();
Class<?> clazz = p.getClass();
System.out.println(clazz.getName()); // Stamperà: Person oppure com.example.Person
Tabella: modi principali per ottenere un oggetto Class
| Come ottenerlo | Quando usarlo | Esempio |
|---|---|---|
|
Se la classe è nota in fase di compilazione | |
|
Se hai un oggetto | |
|
Se il nome della classe è noto a runtime | |
Schema: un oggetto e il suo Class
+-------------------+
| Person p |
+-------------------+
|
v
+-------------------+
| p.getClass() |
+-------------------+
|
v
+-------------------+
| Class<Person> |
+-------------------+
Verifica del tipo tramite Class
A volte serve sapere se un oggetto appartiene a una classe specifica:
if (p.getClass() == Person.class) {
System.out.println("È sicuramente Person!");
}
Oppure, tenendo conto dell’ereditarietà — il normale operatore instanceof:
if (p instanceof Person) {
// funziona come al solito
}
3. Quando serve la reflection e quando — no
Da un grande potere derivano grandi responsabilità. La reflection è uno strumento potente, ma va usata solo quando è davvero necessaria.
Dove la reflection è indispensabile
- Framework e librerie: Spring, Hibernate, JUnit, Jackson — automazione, «magia» e meno codice «manuale».
- Serializzazione: Serve conoscere i campi dell’oggetto per trasformarlo in JSON/XML.
- Plugin e estensioni: Le classi vengono caricate dinamicamente e la loro struttura non è nota in anticipo.
Perché non abusarne
- Perdita di type safety: Gli errori emergono a runtime.
- Manutenibilità ridotta: Il codice diventa meno chiaro.
- Prestazioni: Più lenta delle chiamate dirette.
- Sicurezza: Si può aggirare l’incapsulamento ed esporre dati privati.
Esempio: quando la reflection non serve
p.sayHello();
Esempio: quando la reflection è indispensabile
Stai scrivendo un mini‑framework per il testing: l’utente annota i metodi con @Test e il programma deve trovarli e invocarli automaticamente — senza reflection è impossibile.
4. Dimostrazione: familiarizzare con Class
Esempio: stampa del nome della classe e della sua superclasse
public class ReflectionDemo {
public static void main(String[] args) {
String s = "Hello, reflection!";
Class<?> clazz = s.getClass();
System.out.println("Nome della classe: " + clazz.getName());
System.out.println("Nome semplice della classe: " + clazz.getSimpleName());
System.out.println("Package: " + clazz.getPackageName());
System.out.println("Superclasse: " + clazz.getSuperclass().getName());
}
}
Risultato:
Nome della classe: java.lang.String
Nome semplice della classe: String
Package: java.lang
Superclasse: java.lang.Object
Esempio: ottenere il Class per i tipi primitivi
Class<Integer> intClass = int.class;
System.out.println(intClass.getName()); // int
Class<?> doubleClass = double.class;
System.out.println(doubleClass.getName()); // double
Esempio: ottenere il Class di un array
int[] arr = new int[10];
Class<?> arrClass = arr.getClass();
System.out.println(arrClass.getName()); // [I (formato specifico per gli array)
Esempio: verifica dell’appartenenza al tipo
if (arrClass.isArray()) {
System.out.println("È un array!");
}
5. Pratica: mini‑programma «Che classe è?»
Scriviamo un programma che accetta il nome completo della classe (per esempio, "java.util.ArrayList") e stampa le informazioni principali su di essa.
import java.util.Scanner;
public class ClassInfoPrinter {
public static void main(String[] args) throws Exception {
Scanner scanner = new Scanner(System.in);
System.out.print("Inserisci il nome completo della classe: ");
String className = scanner.nextLine();
Class<?> clazz = Class.forName(className);
System.out.println("Nome della classe: " + clazz.getName());
System.out.println("Package: " + clazz.getPackageName());
System.out.println("Superclasse: " + clazz.getSuperclass().getName());
Class<?>[] interfaces = clazz.getInterfaces();
System.out.print("Implementa le interfacce: ");
for (Class<?> i : interfaces) {
System.out.print(i.getName() + " ");
}
System.out.println();
}
}
Esempio di esecuzione:
Inserisci il nome completo della classe: java.util.ArrayList
Nome della classe: java.util.ArrayList
Package: java.util
Superclasse: java.util.AbstractList
Implementa le interfacce: java.util.List java.util.RandomAccess java.lang.Cloneable java.io.Serializable
Curiosità su Class e reflection
- Per ogni tipo caricato nella JVM c’è un unico rappresentante — un oggetto Class. Tutte le istanze dello stesso tipo condividono lo stesso Class<T>.
- Si può determinare la natura del tipo: isInterface(), isEnum(), isArray(), isPrimitive() ecc.
- Se si indica un nome errato in Class.forName, si ottiene l’eccezione ClassNotFoundException.
- Con la reflection si possono creare oggetti anche senza conoscere la classe in fase di compilazione — ne parleremo nella prossima lezione.
6. Errori tipici al primo incontro con la reflection
Errore n. 1: Aspettarsi che la reflection sia veloce.
In realtà la reflection è più lenta delle chiamate di metodi e degli accessi ai campi tradizionali. Non usatela per ogni piccolezza.
Errore n. 2: Tentare di ottenere un Class per una classe inesistente.
Se sbagliate nome, otterrete ClassNotFoundException. Gestite questa eccezione.
Errore n. 3: Confondere l’oggetto Class con un’istanza.
Un oggetto Class descrive la struttura del tipo e non è una sua istanza.
Errore n. 4: Usare la reflection senza necessità.
Se si può fare con codice normale — meglio così. La reflection serve per dinamica, framework, plugin e compiti simili.
Errore n. 5: Gestione disattenta di campi e metodi privati.
La reflection permette di aggirare l’incapsulamento, il che può portare a errori e problemi di sicurezza, soprattutto in progetti grandi.
GO TO FULL VERSION