L'introspection

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.

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

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. A 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 librairie, 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 :

 
Sélectionnez

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 le 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 arguments. 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 apartenant 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 indispensable pour profiter des nouvelles fonctionnalités d'un JDK sans pour autant briser la compatibilité avec les versions précédentes.

listing 1
Sélectionnez

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
Sélectionnez

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
Sélectionnez

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
Sélectionnez

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 reflection
La puissance de la reflection
Sur un JDK 1.4, cette application propose des onglets défilant et des fenêtres particulières
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
Des langages de scripting comme BeanShell ou Jython emploient l'introspection
Python est également doué d'introspection
Python est également doué d'introspection

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

Articles et tutoriels Java
L'essentiel de Java en une heure
L'API java.nio du JDK 1.4
Inversion de contrôle en Java
L'introspection
Le Java Community Process
Conception de tests unitaires avec JUnit
Les Strings se déchaînent
Présentation de SWT
La programmation réseau en Java avec les sockets
Du bon usage de l'héritage et de la composition
Les références et la gestion de la mémoire en Java
Constructeurs et méthodes exportées en Java
Les membres statiques, finaux et non immuables en Java
Les classes et objets immuables en Java
Comprendre et optimiser le Garbage Collector
Les principes de la programmation d'une interface graphique
Les opérateurs binaires en Java
Prenez le contrôle du bureau avec JDIC
Les Java Data Objects (JDO version 1.0.1)
La persistance des données avec Hibernate 2.1.8
Journalisation avec l'API Log4j
Java 5.0 et les types paramétrés
Les annotations de Java 5
Java 1.5 et les types paramétrés
Créer un moteur de recherche avec Lucene
Articles et tutoriels Swing
Threads et performance avec Swing
Rechercher avec style en utilisant Swing
Splash Screen avec Swing et Java3D
Drag & Drop avec style en utilisant Swing
Attendre avec style en utilisant Swing
Mixer Java3D et Swing
Articles et tutoriels Java Web
Redécouvrez le web avec Wicket
Cette création est mise à disposition sous un contrat Creative Commons (Paternité - Partage des Conditions Initiales à l'Identique).