CodeGym /Cours /JAVA 25 SELF /Création d’objets via la réflexion

Création d’objets via la réflexion

JAVA 25 SELF
Niveau 62 , Leçon 3
Disponible

1. Création d’objets via la réflexion

Parfois, nous devons créer des objets sans connaître leur type à la compilation. Par exemple, le nom de la classe vient de la configuration, ou nous écrivons un framework générique (sérialiseur, conteneur DI). Dans du code habituel, nous écririons :

User user = new User("John", 25);

Mais que faire si nous ne savons pas que la classe s’appelle User, ni les paramètres de son constructeur ? La réflexion nous aide dans ce cas.

Approche obsolète : Class.newInstance()

Autrefois, Java proposait la méthode Class.newInstance(), qui crée un objet via un constructeur public sans paramètres :

Class<?> clazz = Class.forName("com.example.User");
Object obj = clazz.newInstance();

IMPORTANT : Cette méthode est marquée comme deprecated depuis Java 9 et n’est pas recommandée. Elle ne permet pas de choisir le constructeur, masque les causes des erreurs et exige uniquement un public-constructeur sans paramètres.

Approche moderne : via les constructeurs

Dans le code réel, il n’existe pas toujours un public-constructeur sans paramètres. L’approche actuelle consiste à obtenir le constructeur voulu via getConstructor(...) ou getDeclaredConstructor(...), puis à appeler dessus newInstance(...).

Exemple : création d’un objet avec des paramètres

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");
// On obtient le constructeur avec les paramètres String, int
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
// On crée l’objet, on passe les paramètres
Object user = constructor.newInstance("John", 25);

System.out.println(user); // toString, s’il est défini

Que se passe‑t‑il ici ?

  • Nous obtenons le constructeur requis par sa signature.
  • Nous appelons dessus newInstance(...), en passant les arguments.
  • Nous obtenons un objet de type Object (on peut le caster en User si le type est connu).

Et si le constructeur est privé ?

Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true); // Magie ! On peut maintenant appeler le constructeur privé.
Object user = constructor.newInstance("John", 25);

ATTENTION : Il ne faut pas en abuser — cela enfreint l’encapsulation. Dans les applications modulaires (Java 9+) des restrictions d’accès s’appliquent également.

Tableau comparatif des méthodes de création d’objets

Méthode Vitesse Sécurité des types Quand l’utiliser
new User()
La plus rapide Totale Toujours, quand c’est possible
Constructor.newInstance()
Lente Perdue Frameworks, plug‑ins
MethodHandle
Moyenne Partielle Bibliothèques performantes
Fabriques Rapide Totale Création d’objets flexible

2. Appeler des méthodes via la réflexion

Vous pouvez appeler n’importe quelle méthode d’un objet, même si vous ne connaissez pas son nom à la compilation, ou si elle est private.

Récupération d’une méthode

import java.lang.reflect.Method;

Class<?> clazz = user.getClass();
// On obtient une méthode public par son nom et les types de paramètres
Method method = clazz.getMethod("getName"); // Sans paramètres
Object result = method.invoke(user); // Appel de méthode sans paramètres

System.out.println(result); // Affiche le nom de l’utilisateur

Appel d’une méthode avec paramètres

Method setName = clazz.getMethod("setName", String.class);
setName.invoke(user, "Peter"); // On définit un nouveau nom

Appel d’une méthode privée

Method secret = clazz.getDeclaredMethod("secretMethod", int.class);
secret.setAccessible(true); // On lève la protection
Object secretResult = secret.invoke(user, 123);

Important : Tous les paramètres sont passés comme un tableau d’objets (varargs). Si la méthode retourne quelque chose — le résultat arrive comme Object.

3. Accès aux champs via la réflexion

Parfois, il faut lire ou modifier la valeur d’un champ, même s’il est private (par exemple lors de la sérialisation ou des tests).

Récupération d’un champ

import java.lang.reflect.Field;

Class<?> clazz = user.getClass();
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true); // Si le champ n’est pas public

// Lecture de la valeur
Object age = ageField.get(user);
System.out.println("Âge: " + age);

// Modification de la valeur
ageField.set(user, 42);
System.out.println("Nouvel âge: " + ageField.get(user));

Travail avec des champs statiques

Si le champ est statique, passez à get/set la valeur null à la place de l’objet :

Field staticField = clazz.getDeclaredField("counter");
staticField.setAccessible(true);
staticField.set(null, 100); // Pour les champs static, pas besoin d’objet

4. Limitations et exceptions

Le travail avec la réflexion peut s’accompagner de nombreuses exceptions vérifiées (checked). Les plus courantes :

  • ClassNotFoundException — aucune classe portant ce nom n’a été trouvée.
  • NoSuchMethodException — pas de constructeur ni de méthode avec cette signature.
  • NoSuchFieldException — champ introuvable.
  • IllegalAccessException — accès au membre impossible (par exemple, méthode privée sans setAccessible(true)).
  • InstantiationException — la classe est abstraite ou c’est une interface ; impossible de créer un objet.
  • InvocationTargetException — une erreur s’est produite dans le constructeur ou la méthode appelés.

Exemple de gestion :

try {
    // ... votre code utilisant la réflexion ...
} catch (ReflectiveOperationException e) {
    e.printStackTrace();
}

Si les détails importent peu, il est plus pratique d’attraper la super‑classe commune ReflectiveOperationException (Java 7+).

5. Pratique : mini‑programme « Instanciateur dynamique »

Exemple de classe pour les essais

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("Salut, je m’appelle " + name + ", j’ai " + age + " ans.");
    }
}

Création et manipulation dynamiques

import java.lang.reflect.*;

public class ReflectionDemo {
    public static void main(String[] args) {
        try {
            // 1. On obtient l’objet Class par son nom
            Class<?> clazz = Class.forName("Person");

            // 2. On récupère le constructeur et on crée l’objet
            Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
            Object person = constructor.newInstance("Alice", 30);

            // 3. On appelle la méthode privée sayHello
            Method sayHello = clazz.getDeclaredMethod("sayHello");
            sayHello.setAccessible(true);
            sayHello.invoke(person); // Salut, je m’appelle Alice, j’ai 30 ans.

            // 4. On modifie le champ privé name
            Field nameField = clazz.getDeclaredField("name");
            nameField.setAccessible(true);
            nameField.set(person, "Bob");

            // 5. On appelle à nouveau sayHello
            sayHello.invoke(person); // Salut, je m’appelle Bob, j’ai 30 ans.

        } catch (ReflectiveOperationException e) {
            e.printStackTrace();
        }
    }
}

À noter :

  • Tout est fait sans connaissance directe du type Person dans le code.
  • On peut modifier des champs privés et appeler des méthodes privées (si cela est autorisé par la politique de sécurité de la JVM et la configuration modulaire).

6. Quel lien avec les applications réelles ?

La réflexion est à la base de nombreuses bibliothèques et frameworks populaires :

  • JUnit : repérage des méthodes annotées @Test, création d’instances de tests et appels de méthodes.
  • Spring : DI, création de beans, autowiring.
  • Jackson, Gson : sérialisation/désérialisation des champs d’objets.
  • Hibernate : accès aux champs des entités, proxies et chargement paresseux.

7. Schéma visuel : comment un objet est créé via la réflexion

flowchart TB
    A["Nom de classe (String)"] --> B["Class.forName"]
    B --> C["Class<?>"]
    C --> D["getConstructor(...)"]
    D --> E["Constructor<?>"]
    E --> F["newInstance(...)"]
    F --> G["Object"]

8. Erreurs typiques lors du travail avec la réflexion

Erreur n° 1 : Mauvais constructeur. Vous demandez un constructeur avec une mauvaise signature, par exemple, getConstructor(String.class) alors qu’il n’existe qu’un constructeur avec deux paramètres — vous obtiendrez NoSuchMethodException. Vérifiez toujours les signatures !

Erreur n° 2 : Absence d’accès. Appeler un constructeur/méthode privés sans setAccessible(true) mène à une IllegalAccessException. Et n’oubliez pas : à partir de Java 9, le système modulaire peut imposer des restrictions supplémentaires.

Erreur n° 3 : Problèmes de types. Tous les paramètres et valeurs de retour via la réflexion sont des Object. Un mauvais cast — ClassCastException.

Erreur n° 4 : Exceptions non gérées. La réflexion lance beaucoup d’exceptions vérifiées. Gérez‑les, par exemple via la ReflectiveOperationException commune, sinon le code ne compilera pas.

Erreur n° 5 : Violation de l’encapsulation. L’utilisation irréfléchie de setAccessible(true) — « accès au frigo d’autrui ». N’en usez qu’en cas de réelle nécessité et tenez compte des exigences de sécurité.

Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION