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 |
|---|---|---|---|
|
La plus rapide | Totale | Toujours, quand c’est possible |
|
Lente | Perdue | Frameworks, plug‑ins |
|
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é.
GO TO FULL VERSION