Date de première publication : 2010/05/10
Derrière le terme anglais de reflection se cachent en Français deux notions : l’introspection et l’intercession. Un programme JAVA s’exécute dans une machine virtuelle : il est possible de consulter des informations sur les classes (attributs, méthodes et constructeurs) à l’exécution : cela s’appelle l’introspection. Cette notion se rapproche de RTTI (Runtime Type Information) du C++, vu en ZZ3.
Si le gestionnaire de sécurité le permet, on peut également "modifier" les classes dynamiquement, c’est l’intercession.
Pour quasiment tous les types d’éléments que l’on peut manipuler, il y a une classe correspondante :
Class
, AccessibleObject
, Constructor
, Method
et Field
... (On a une représentation objet de l'objet :-))
On peut choisir de lister soit tous les éléments publics, soit tous les éléments (declared
), [mode ="python"] ce qui n'est pas respectueux de l'encapsulation [fin mode]
Découverte de l'introspection
Utilisation de classes "maison"
- Créer une classe
Personne
avec un attributnom
de type chaîne de caractères et deux constructeurs : un sans argument (avec initialisation par défaut) et l'autre qui initialise le nom - Créer une classe
Enfant
qui dérive dePersonne
. - Le constructeur de
Personne
doit afficher le nom de la classe de l’objet créé. - Instancier un objet de chaque classe et vérifier l'affichage sur la console
Personne p = new Personne();
On peut le faire car tout objet connaît sa classe grâce à la méthode getClass()
.
Pour les types primitifs et void
, il suffit d’utiliser le "champ" class
pour avoir la même information.
objet.getClass()
UneClasse.class
int.class
- Charger la classe
Personne
en mémoire grâce à la méthodeforName()
. D'habitude, les classes que l'on utilise sont chargées automatiquement par la machine virtuelle à partir du moment où la machine estime qu'elle en a besoin à partir du code écrit et compilé par nos soins. L'intérêt de la méthodeforName()
est de pouvoir charger une classe avec un nom déterminé à l'exécution (et pas à la compilation)
Class classe = Class.forName("paquetage.nom_de_la_classe");
- Créer une instance de
Personne
grâce ànewInstance()
. Vérifier que le constructeur sans argument est appelé.
Object o = classe.newInstance(); // Java 8
Object o = classe.getDeclaredConstructor().newInstance(); // Java 9 et +
Si vous avez une erreur sur l'exception InvocationTargetException
, avez-vous pensé ...
à importer le bon package
- Vérifier lors de l’exécution que cette instance n’est pas une instance de la classe
Enfant
.
System.out.println((objet instanceof Enfant)?"vrai":"faux");
- Charger la classe
Enfant
. Créer un objet de cette classe et refaire les étapes précédentes. - Afficher le nom de la classe mère de l’objet de classe
Enfant
- Lister par introspection les constructeurs de la classe
Personne
. - Pouvez-vous instancier un objet de la classe
Personne
avecnewInstance()
et des arguments ?
Object o = classe.getConstructor(String.class).newInstance();
Introspection sur une classe existante [OPTIONNEL]
Choisissez un classe que vous connaissez, par exemple java.lang.Double
:
- Lister les interfaces que la classe implémente,
- Lister les méthodes de la classe,
- Lister les attributs/champs de la classe. Vérifier la visibilité des éléments.
Application : programmation d'un système de greffons
Nous allons mettre en œuvre le principe de l’introspection et surtout du chargement dynamique de
classe pour produire une toute petite application dont le code n’est pas entièrement connu à la
compilation. Un utilisateur pourra fournir des algorithmes à appliquer sur un tableau de double
s lors
de l’exécution du programme.
Greffon non générique
- Créer une interface
Algorithme
qui dispose d’une méthodeappliquer(double d[])
. - Écrire une classe
Afficher
qui implémente l’interface définie précédemment et dont la méthodeappliquer()
affiche le tableau passé en paramètre. Nous sommes en train d’écrire un foncteur : autrement dit une classe qui encapsule une fonction. - Écrire un programme qui déclare un tableau de
double
et initialise ce tableau avec des valeurs pseudo aléatoires. L’utilisateur pourra spécifier le nom de toute classe implémentant l’interfaceAlgorithme
(saisie ou paramètre à l'exécution). Si la classe respecte bien l’interface, alors l’algorithme sera appliqué sinon le tableau sera seulement affiché. - Créer une classe
Trier
qui implémente l’interfaceAlgorithme
. La méthodeappliquer()
réalisera le tri rapide du tableau. Cette fonction existe, je vous laisse googler pour la trouver. - Tester votre programme principal avec cette nouvelle classe.
String nom_algo = "Trier";
// le nom peut être lu au clavier ou donné en paramètre à l'exécution
// charger la classe
// verifier son type
// exécuter
Si vous voulez lire des données au clavier, vous pouvez utiliser une Console
dans un programme standalone
mais pas sous Eclipse. La console n'est pas disponible. Le plus simple est de passer le nom de la classe en paramètre à la méthode de classe main()
Dans un programme Java classique, le fait d'utiliser une classe dans le code fait qu'elle est compilée si ce n'est pas déjà fait. Les classes qui implémentent l'interface Algorithme
ne sont pas compilées automatiquement car elles ne sont pas liées au code qui s'exécute. Compilez-les avant de les donner à votre programme principal !!!