W3docs

Introduction à la Reflection Java

Ce qu'est la reflection en Java, quand l'utiliser et un aperçu du package java.lang.reflect.

La Reflection est la capacité d'un programme à inspecter et manipuler sa propre structure à l'exécution : demander à une classe quels champs, méthodes et constructeurs elle possède, lire et écrire ces champs, appeler ces méthodes, et créer de nouvelles instances — tout cela sans nommer les types à la compilation. Là où le Java ordinaire est statique (le compilateur connaît chaque type que vous manipulez), la reflection est dynamique (vous découvrez les types depuis des chaînes, une configuration ou ce qui est chargé à l'exécution). Cette partie du livre est une exploration de java.lang.reflect ; ce chapitre pose le décor.

Ce que la reflection vous permet de faire

Le code normal nomme le type avec lequel il travaille :

User u = new User("ada");
String name = u.getName();

Le code réflectif atteint le même résultat sans écrire User ou getName comme tokens connus à la compilation — ce sont des chaînes résolues à l'exécution :

Class<?> cls = Class.forName("com.example.User");
Object u = cls.getDeclaredConstructor(String.class).newInstance("ada");
Object name = cls.getMethod("getName").invoke(u);

La seconde forme est beaucoup plus verbeuse et beaucoup plus lente, et elle abandonne la vérification de types à la compilation. Vous ne l'écririez jamais pour une logique d'application ordinaire. Vous y recourez précisément lorsque le type n'est pas connu avant l'exécution.

Quand la reflection justifie son usage

La reflection est le moteur des frameworks, pas du code quotidien. Utilisations typiques :

  • Injection de dépendances (Spring, Guice, CDI) : le conteneur lit les annotations et les signatures de constructeurs, puis instancie et câble des beans dont il n'a jamais vu le code source.
  • Sérialisation (Jackson, Gson) : une bibliothèque JSON parcourt les champs d'un objet pour les lire ou les remplir sans que vous écriviez du code de correspondance spécifique à chaque classe.
  • ORM (Hibernate, JPA) : associe les colonnes aux champs par reflection sur une classe entité.
  • Exécuteurs de tests (JUnit) : trouve les méthodes annotées @Test et les invoque.
  • Systèmes de plugins : charge une classe nommée dans un fichier de configuration et appelle une méthode d'interface connue sur celle-ci.

Le fil conducteur : un mécanisme général opérant sur des types utilisateurs arbitraires contre lesquels il n'a pas été compilé. C'est exactement le problème que la reflection résout.

Les types principaux dans java.lang.reflect

La reflection commence à partir d'un objet Class (traité dans le chapitre suivant, Objets Class Java) et se ramifie en une petite famille de classes, chacune décrivant un type de membre :

TypeReprésenteObtenu depuis Class via
Fieldun champgetField / getDeclaredField(s)
Methodune méthodegetMethod / getDeclaredMethod(s)
Constructor<T>un constructeurgetConstructor / getDeclaredConstructor(s)
Parameterun paramètre de méthode/constructeurExecutable.getParameters()
Modifierun assistant pour le bitset de modificateurs intméthodes statiques

Field, Method et Constructor partagent une hiérarchie de superclasses commune : Member (l'interface) et AccessibleObject (qui porte setAccessible). Cette base partagée explique pourquoi « le rendre accessible » et « lire ses annotations » paraissent identiques quel que soit le membre que vous détenez.

La distinction get… vs getDeclared…

Presque chaque méthode de recherche existe en deux variantes, et la distinction compte dans chaque chapitre suivant :

  • getField / getMethod / getConstructor — retourne les membres publics, y compris ceux hérités des superclasses et interfaces.
  • getDeclaredField / getDeclaredMethod / getDeclaredConstructor — retourne les membres de tout niveau d'accès (private, protected, package), mais uniquement ceux déclarés sur cette classe exacte — rien d'hérité.

Ainsi, getMethods() voit une méthode publique héritée d'un parent mais pas une méthode utilitaire private de la classe elle-même ; getDeclaredMethods() voit la méthode utilitaire private mais pas la méthode publique héritée. Pour atteindre un membre privé hérité, vous remontez via getSuperclass() en appelant getDeclared… à chaque niveau.

Le coût : performance, sécurité et encapsulation

La reflection est puissante, et chaque pouvoir a un prix.

  • Performance. Les appels réflectifs sont plus lents que les appels directs — les recherches de méthodes/champs, les vérifications d'accès et le boxing d'arguments génèrent tous des surcharges. Le JIT optimise bien les appels réflectifs fréquents, mais la reflection dans une boucle serrée est un signal d'alarme. Mettez en cache les objets Method/Field ; ne les recherchez jamais à chaque appel.
  • Pas de sécurité à la compilation. Une faute de frappe dans un nom de méthode se compile sans erreur et échoue à l'exécution avec NoSuchMethodException. Les outils de refactorisation renomment getName partout — sauf à l'intérieur de votre chaîne "getName".
  • Rupture de l'encapsulation. setAccessible(true) vous permet de lire et d'écrire un état private. C'est ainsi que les sérialiseurs remplissent des champs sans setter, mais cela vous couple à des éléments internes que l'auteur de la classe n'a jamais promis de garder stables.
  • Restrictions des modules. Depuis Java 9, le système de modules peut refuser l'accès réflectif aux packages non exportés. Appeler setAccessible(true) à travers une frontière de module qui n'a pas opens le package lève une InaccessibleObjectException.

Un exemple concret : un mini-dumper d'objets générique

Pour rendre la portée concrète, voici une routine réflective unique qui affiche les champs de n'importe quel objet et leurs valeurs — le genre de chose qu'un débogueur ou une bibliothèque de journalisation fait. Elle ne nomme aucun type applicatif ; elle fonctionne sur tout ce qu'on lui passe.

java— editable, runs on the server

Ce qu'il faut retenir de l'exécution :

  • La méthode dump n'a nommé aucun type concret et a pourtant affiché Point et User. Son seul contrat est « donnez-moi un Object » ; tout ce qui concerne la structure — noms de champs, types, valeurs — venait de getClass() à l'exécution. C'est le geste fondateur de la reflection : une routine, des entrées arbitraires.
  • getDeclaredFields() a retourné tous les champs y compris les private, mais leur lecture nécessitait d'abord setAccessible(true). Sans cet appel, f.get(obj) sur un champ privé lève IllegalAccessException. La recherche et l'accès sont deux portes distinctes.
  • Modifier.toString(f.getModifiers()) a converti le bitset brut de modificateurs en texte lisible comme private final. Les modificateurs sont stockés sous forme d'int de bits de drapeau ; l'assistant Modifier les décode pour éviter de tester les bits à la main.
  • Le troisième objet a été construit sans aucun new User(...) dans le source — Class.forName("…$User") a résolu la classe imbriquée depuis une chaîne (notez le séparateur $ pour les types imbriqués), et getDeclaredConstructor(...).newInstance(...) l'a construite. C'est le pattern de chargement de plugin en miniature : un nom en entrée, un objet en sortie.
  • Lire la valeur du champ (f.get(obj)) et lire les métadonnées du champ (f.getName(), f.getModifiers()) sont indépendants. Les métadonnées n'ont besoin ni d'instance ni d'accessibilité ; les valeurs nécessitent l'objet et, pour les champs privés, le drapeau d'accessibilité.

Comment le reste de cette partie est organisé

Chaque chapitre restant zoom sur un aspect particulier :

  • Objets Class — les trois façons d'obtenir un Class<T> et ce qu'il vous dit.
  • Champs — inspecter, lire et écrire des champs (y compris private et final).
  • Méthodes — trouver et invoquer des méthodes, résolution de surcharge, valeurs de retour.
  • Constructeurs — construire des instances par reflection, y compris les constructeurs privés.
  • Annotations — lire les métadonnées que vous avez attachées avec les annotations, à l'exécution.
  • Proxies dynamiques — synthétiser des implémentations complètes d'interface à l'exécution.

Tout au long, gardez à l'esprit le compromis : la reflection est le bon outil lorsque le type n'est genuinement pas connu avant l'exécution, et le mauvais outil lorsqu'il l'est. Le chapitre suivant commence à la racine de tout — l'objet Class.

Pratique

Pratique
Une bibliothèque de journalisation doit afficher les valeurs des champs de tout objet passé à sa méthode 'log(Object o)', y compris des objets dont les classes n'ont jamais été compilées avec elle et dont les champs sont 'private'. Quelle combinaison est l'approche minimale correcte ?
Une bibliothèque de journalisation doit afficher les valeurs des champs de tout objet passé à sa méthode 'log(Object o)', y compris des objets dont les classes n'ont jamais été compilées avec elle et dont les champs sont 'private'. Quelle combinaison est l'approche minimale correcte ?
Was this page helpful?