L'introspection

Romain Guy (Gfx)

La plate-forme Java offre une notion avancée de programmation nommée introspection, ou en anglais reflection. Cette notion permet de découvrir à l'exécution la nature des objets de l'environnement et d'agir sur eux sans pour autant les connaître.

- Titre : L'introspection

- Auteur : Romain Guy (Gfx)

- Parution : 29 mai 2007

- Mise à jour : 16 novembre 2019

- Licence : Cette création est mise à disposition sous un contrat Creative Commons (Paternité - Partage des Conditions Initiales à l'Identique).

Sommaire

- I. Introduction

- II. L'API Java

- III. Les classes

- IV. Invoquer une méthode

- V. Screenshots

I - Introduction

Le compilateur de la plate-forme Java, c'est-à-dire le compilateur javac par défaut, produit un ensemble de fichiers .class à partir du code source de l'application. Ces fichiers contiennent un bytecode, interprété par la machine virtuelle à l'exécution du programme. À l'instar d'autres langages interprétés (ou semi-interprétés), tels que le Python, ce principe permet l'introspection. L'introspection possède de nombreuses applications pratiques. Elle peut servir à charger dynamiquement des jeux de classes durant l'exécution, à proposer des fonctionnalités spécifiques à l'une ou l'autre des versions d'une bibliothèque, et ainsi de suite…

II - L'API Java

Les classes nécessaires à la mise en pratique du phénomène d'introspection résident dans le package java.lang.reflect. En outre, vous nécessiterez la classe java.lang.Class. Examinons le cas d'un interpréteur de scripts implémenté en Java. Cet interpréteur propose un ensemble de fonctions par défaut réparties dans des packages différents. Le chargement d'un package nécessite la lecture d'un fichier indiquant le nom complet des classes Java correspondant aux implémentations des fonctions. Nous pourrions avoir à traiter un tel fichier :

com.loginmag.lang.math.Exp
com.loginmag.lang.math.Ln
com.loginmag.lang.math.Pow

Puisque chacune de ces classes hérite d'une classe Function, nous pourrions rédiger les instructions présentées dans le listing numéro un. Le vecteur lignes correspond à un vecteur de chaînes de caractères désignant le contenu de chaque ligne du fichier du package. La méthode statique forName() de la classe java.lang.Class sert à obtenir le type lié au nom de classe donné en paramètre. Par exemple, l'instruction Class.forName("java.lang.String") est en tout point équivalente à "chaine".getClass(). Chaque objet possède une méthode getClass() retournant son type. Vous pouvez également accéder au champ statique public "class" (exemple : Integer.class). De la sorte, si l'objet Class retourné par un appel à cette méthode se veut différent de l'objet null, vous pouvez être certain que la classe demandée existe.

III - Les classes

Maintenant que vous possédez un lien vers la classe désirée, Java vous offre de nombreuses possibilités. La première concerne la création d'une instance de la classe. La méthode la plus simple réside dans l'appel de la méthode newInstance(). Ceci correspond à l'invocation du constructeur par défaut, sans argument. Nous pouvons également obtenir un constructeur précis ainsi que le fait le code du listing deux. Ce programme crée une nouvelle instance de la classe java.awt.Dimension de la même manière que si nous avions écrit "new Dimension(20, 20)". La méthode getDeclaredConstructor() reçoit en argument un vecteur de Class marquant le type des paramètres du constructeur désiré. Nous découvrons ici une singularité de Java qui autorise l'écriture de int.class, ou encore de double.class, boolean.class… De plus, nous employons ici un constructeur recevant en paramètres des types primitifs entiers. Toutefois, lors de l'appel de newInstance() nous offrons un vecteur d'objets recélant des Integer. Le passage de types primitifs à un constructeur ou une méthode en matière d'introspection se réalise par l'entremise des enveloppes de java.lang : Boolean, Character, Integer, Double…
Outre les constructeurs, la classe Class permet de découvrir le parent d'une classe, méthode getSuperclass(), son nom, getName(), son package, getPackage() ou ses interfaces getInterfaces(). Depuis l'instance Class d'un objet donné, vous pouvez découvrir l'ensemble de ses caractéristiques. Le listing numéro trois propose ainsi l'affichage de la hiérarchie complète d'une classe donnée. Pour chaque classe de la hiérarchie, la signature de ses méthodes est affichée. La méthode getDeclaredMethods() retourne un vecteur de java.lang.reflect.Method désignant les méthodes propres à la classe. Une méthode similaire, getMethods(), retourne toutes les méthodes de la classe, en incluant les méthodes appartenant aux classes parentes.

IV - Invoquer une méthode

L'invocation d'une méthode par le principe de l'introspection ressemble énormément à la création d'une nouvelle instance par l'intermédiaire d'un objet Constructor. Vous pouvez obtenir une méthode d'une classe par le biais de getDeclaredMethod() ou de getMethod(). Les différences sont les mêmes que celles évoquées précédemment. Ces deux méthodes demandent en argument une chaîne désignant le nom de la méthode et un vecteur de classe afin de déterminer les paramètres. Depuis la version 1.4 du JDK, les panneaux d'onglets proposent une option de défilement qui évite de les afficher sur plusieurs lignes. Ce comportement se détermine par un appel à la méthode setTabLayoutPolicy() apparue dans le JDK 1.4. Nous pouvons dès lors créer une méthode qui activera ce comportement que si l'utilisateur possède la version nécessaire du JDK (voir listing 4). Vous pourrez constater que ce morceau de code récupère une constante de la classe JTabbedPane par introspection. La classe Method garantit un accès à toutes les informations relatives à une méthode donnée. Nous pouvons ainsi connaître le modificateur d'accès d'une méthode (classe Modifier, correspond aux mot-clés public, private…), son type de retour et les exceptions susceptibles d'être générées.
La puissance et la qualité des fonctions d'introspection se révèlent indispensables pour profiter des nouvelles fonctionnalités d'un JDK sans pour autant briser la compatibilité avec les versions précédentes.

listing 1

for (int i = 0; i < lignes.length; i++)
{
Class c = Class.forName(lignes[i]);
if (c != null)
{
Object f = c.newInstance();
if (f instanceof Function)
ajouterFonction(f);
}
}

listing 2

Class clazz = Dimension.class;
Constructor constr = clazz.getDeclaredConstructor(new Class[] { int.class, int.class });
Object o = constr.newInstance(new Object[] { new Integer(20), new Integer(20) });
Dimension d = (Dimension) o;
System.out.println("w : " + d.width + "; h : " + d.height);

listing 3

public static void print(Class clazz)
{
Class superclazz = null;
System.out.println("Hierarchie de " + clazz.getName() + "\n");
superclazz = clazz.getSuperclass();

while (superclazz != null)
{
System.out.print("-> " + superclazz.getName() + " [ ");

Class[] interfaces = superclazz.getInterfaces();
for (int i = 0; i < interfaces.length; i++)
System.out.print(interfaces[i].getName() + " ");
System.out.println("]");

Method[] methods = superclazz.getDeclaredMethods();
for (int i = 0; i < methods.length; i++)
System.out.println("  + " + methods[i]);

superclazz = superclazz.getSuperclass();
}
}

listing 4

public static void setScrollableTabbedPane(JTabbedPane pane)
{
try
{
Class cl = pane.getClass();
Method m = cl.getMethod("setTabLayoutPolicy", new Class[] { int.class });
if (m !=  null)
{
Field f = cl.getField("SCROLL_TAB_LAYOUT");
m.invoke(pane, new Object[] { new Integer(f.getInt(pane)) });
}
} catch (Exception e) { }
}

V - Screenshots

La puissance de la réflexion

Sur un JDK 1.4, cette application propose des onglets défilant et des fenêtres particulières

Des langages de scripting comme BeanShell ou Jython emploient l'introspection

Python est également doué d'introspection