CodeGym /Corsi /JAVA 25 SELF /Creazione di oggetti tramite reflection

Creazione di oggetti tramite reflection

JAVA 25 SELF
Livello 62, Lezione 3
Disponibile

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
new User()
La più veloce Completa Sempre, quando possibile
Constructor.newInstance()
Lenta Persa Framework e plugin
MethodHandle
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.

1
Compito
JAVA 25 SELF, livello 62, lezione 3
Bloccato
Creazione di una creatura dall'energia pura 🧬
Creazione di una creatura dall'energia pura 🧬
1
Compito
JAVA 25 SELF, livello 62, lezione 3
Bloccato
Creazione magica di antichi volumi 📜
Creazione magica di antichi volumi 📜
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION