Ce cours est visible gratuitement en ligne.

Paperback available in this course

Ce cours existe en eBook.

Certificate of achievement available at the end this course

Got it!
Apprenez à programmer en Java
Last updated on Thursday, June 19, 2014
  • 6 semaines
  • Moyen

Java et la réflexivité

La réflexivité, aussi appelée introspection, consiste à découvrir de façon dynamique des informations relatives à une classe ou à un objet. C'est notamment utilisé au niveau de la machine virtuelle Java lors de l'exécution du programme. En gros, la machine virtuelle stocke les informations relatives à une classe dans un objet.

La réflexivité n'est que le moyen de connaître toutes les informations concernant une classe donnée. Vous pourrez même créer des instances de classe de façon dynamique grâce à cette notion.

Ce chapitre va sûrement vous intéresser ! Alors, allons-y…

L'objet Class

Concrètement, que se passe-t-il ? Au chargement d'une classe Java, votre JVM crée automatiquement un objet. Celui-ci récupère toutes les caractéristiques de votre classe ! Il s'agit d'un objet Class.

Exemple : vous avez créé trois nouvelles classes Java. À l'exécution de votre programme, la JVM va créer un objet Class pour chacune d'elles. Comme vous devez vous en douter, cet objet possède une multitude de méthodes permettant d'obtenir tous les renseignements possibles et imaginables sur une classe.

Dans ce chapitre, nous allons visiter la classe String. Créez un nouveau projet ainsi qu'une classe contenant la méthode main. Voici deux façons de récupérer un objet Class :

public static void main(String[] args) { 
  Class c = String.class;
  Class c2 = new String().getClass();
  //La fameuse méthode finale dont je vous parlais dans le chapitre sur l'héritage
  //Cette méthode vient de la classe Object
}

Maintenant que vous savez récupérer un objet Class, nous allons voir ce dont il est capable. Nous n'allons examiner qu'une partie des fonctionnalités de l'objet Class : je ne vais pas tout vous montrer, je pense que vous êtes dorénavant à même de chercher et de trouver tout seuls. Vous avez l'habitude de manipuler des objets, à présent.

Connaître la superclasse d'une classe

Voici un petit code qui va répondre à la question de la superclasse :

System.out.println("La superclasse de la classe " + String.class.getName() + " est : " + String.class.getSuperclass());

Ce qui nous donne :

La superclasse de la classe java.lang.String est : class java.lang.Object

Notez que la classe Object n'a pas de superclasse. Normal, puisqu'elle se trouve au sommet de la hiérarchie. Donc si vous remplacez la classe String de l'exemple précédent par la classe Object, vous devriez obtenir :

La superclasse de la classe java.lang.Object est : null

En plus de ça, l'objet Class permet de connaître la façon dont votre objet est constitué : interfaces, classe mère, variables…

Connaître la liste des interfaces d'une classe

Vous pouvez tester ce code :

public static void main(String[] args) {                        
  //On récupère un objet Class
  Class c = new String().getClass();
  //Class c = String.class; est équivalent

  //La méthode getInterfaces retourne un tableau de Class
  Class[] faces = c.getInterfaces();
  //Pour voir le nombre d'interfaces
  System.out.println("Il y a " + faces.length + " interfaces implémentées");
  //On parcourt le tableau d'interfaces
  for(int i = 0; i < faces.length; i++)
    System.out.println(faces[i]);
}

Ce qui nous donne la figure suivante.

Récupération des interfaces d'une classe
Récupération des interfaces d'une classe
Connaître la liste des méthodes de la classe

La méthode getMethods() de l'objet Class nous retourne un tableau d'objets Method présents dans le package java.lang.reflect. Vous pouvez soit faire l'import à la main, soit déclarer un tableau d'objets Method et utiliser le raccourci Ctrl + Shift + O.

Voici un code qui retourne la liste des méthodes de la classe String :

public static void main(String[] args) {                                
  Class c = new String().getClass();
  Method[] m = c.getMethods();
                
  System.out.println("Il y a " + m.length + " méthodes dans cette classe");
  //On parcourt le tableau de méthodes
  for(int i = 0; i < m.length; i++)
    System.out.println(m[i]);
}

la figure suivante vous montre un morceau du résultat. Comme vous pouvez le constater, il y a beaucoup de méthodes dans la classe String.

Méthodes de la classe String
Méthodes de la classe String

Vous pouvez constater que l'objet Method regorge lui aussi de méthodes intéressantes. Voici un code qui affiche la liste des méthodes, ainsi que celle de leurs arguments respectifs :

public static void main(String[] args) {                                
  Class c = new String().getClass();
  Method[] m = c.getMethods();
                
  System.out.println("Il y a " + m.length + " méthodes dans cette classe");
  //On parcourt le tableau de méthodes
  for(int i = 0; i < m.length; i++)
  {
    System.out.println(m[i]);

    Class[] p = m[i].getParameterTypes();
    for(int j = 0; j < p.length; j++)
      System.out.println(p[j].getName());

    System.out.println("----------------------------------\n");
  }
}

Le résultat est visible sur la figure suivante. Il est intéressant de voir que vous obtenez toutes sortes d'informations sur les méthodes, leurs paramètres, les exceptions levées, leur type de retour, etc.

Utilisation de l'objet Method
Utilisation de l'objet Method
Connaître la liste des champs (variable de classe ou d'instance)

Ici, nous allons procéder de la même façon qu'avec la liste des méthodes sauf que cette fois, la méthode invoquée retournera un tableau d'objets Field. Voici un code qui affiche la liste des champs de la classe String.

public static void main(String[] args) {                
  Class c = new String().getClass();
  Field[] m = c.getDeclaredFields();

  System.out.println("Il y a " + m.length + " champs dans cette classe");
  //On parcourt le tableau de méthodes
  for(int i = 0; i < m.length; i++)
    System.out.println(m[i].getName());
}

Ce qui nous donne :

Il y a 7 champs dans cette classe
value
offset
count
hash
serialVersionUID
serialPersistentFields
CASE_INSENSITIVE_ORDER
Connaître la liste des constructeurs de la classe

Ici, nous utiliserons un objet Constructor pour lister les constructeurs de la classe :

public static void main(String[] args) {                                
  Class c = new String().getClass();
  Constructor[] construc = c.getConstructors();   
  System.out.println("Il y a " + construc.length + " constructeurs dans cette classe");
  //On parcourt le tableau des constructeurs
  for(int i = 0; i < construc.length; i++){
    System.out.println(construc[i].getName());

  Class[] param = construc[i].getParameterTypes();                        
  for(int j = 0; j < param.length; j++)
    System.out.println(param[j]);

  System.out.println("-----------------------------\n");
  }
}

Vous constatez que l'objet Class regorge de méthodes en tout genre !
Et si nous essayions d'exploiter un peu plus celles-ci ?

Instanciation dynamique

Nous allons voir une petite partie de la puissance de cette classe (pour l'instant). Dans un premier temps, créez un nouveau projet avec une méthode main ainsi qu'une classe correspondant au diagramme en figure suivante.

Classe Paire
Classe Paire

Voici son code Java :

public class Paire {
  private String valeur1, valeur2;

  public Paire(){
    this.valeur1 = null;
    this.valeur2 = null;
    System.out.println("Instanciation !");
  }

  public Paire(String val1, String val2){
    this.valeur1 = val1;
    this.valeur2 = val2;
    System.out.println("Instanciation avec des paramètres !");
  }

  public String toString(){
    return  "Je suis un objet qui a pour valeur : " + this.valeur1 +  " - " + this.valeur2;
  }

  public String getValeur1() {
    return valeur1;
  }
 
  public void setValeur1(String valeur1) {
    this.valeur1 = valeur1;
  }

  public String getValeur2() {
    return valeur2;
  }

  public void setValeur2(String valeur2) {
    this.valeur2 = valeur2;
  }
}

Le but du jeu consiste à créer un objet Paire sans utiliser l'opérateur new.

Pour instancier un nouvel objet Paire, commençons par récupérer ses constructeurs. Ensuite, nous préparons un tableau contenant les données à insérer, puis invoquons la méthode toString().

Regardez comment procéder ; attention, il y a moult exceptions :

public static void main(String[] args) {                                
  String nom = Paire.class.getName();                
  try {
    //On crée un objet Class
    Class cl = Class.forName(nom);
    //Nouvelle instance de la classe Paire
    Object o = cl.newInstance();

    //On crée les paramètres du constructeur
    Class[] types = new Class[]{String.class, String.class};
    //On récupère le constructeur avec les deux paramètres
    Constructor ct = cl.getConstructor(types);

    //On instancie l'objet avec le constructeur surchargé !
    Object o2 = ct.newInstance(new String[]{"valeur 1 ", "valeur 2"} );

  } catch (SecurityException e) {
    e.printStackTrace();
  } catch (IllegalArgumentException e) {
    e.printStackTrace();
  } catch (ClassNotFoundException e) {
    e.printStackTrace();
  } catch (InstantiationException e) {
    e.printStackTrace();
  } catch (IllegalAccessException e) {
    e.printStackTrace();
  } catch (NoSuchMethodException e) {
    e.printStackTrace();
  } catch (InvocationTargetException e) {
    e.printStackTrace();
  }
}

Et le résultat donne la figure suivante.

Instanciation dynamique
Instanciation dynamique

Nous pouvons maintenant appeler la méthode toString() du deuxième objet… Oh et puis soyons fous, appelons-la sur les deux :

public static void main(String[] args) {                                
  String nom = Paire.class.getName();
  try {
    //On crée un objet Class
    Class cl = Class.forName(nom);
    //Nouvelle instance de la classe Paire
    Object o = cl.newInstance();
                                
    //On crée les paramètres du constructeur
    Class[] types = new Class[]{String.class, String.class};
    //On récupère le constructeur avec les deux paramètres
    Constructor ct = cl.getConstructor(types);      
    //On instancie l'objet avec le constructeur surchargé !
    Object o2 = ct.newInstance(new String[]{"valeur 1 ", "valeur 2"} );

    //On va chercher la méthode toString, elle n'a aucun paramètre
    Method m = cl.getMethod("toString", null);
    //La méthode invoke exécute la méthode sur l'objet passé en paramètre
    //Pas de paramètre, donc null en deuxième paramètre de la méthode invoke !
                                
    System.out.println("----------------------------------------");
    System.out.println("Méthode " + m.getName() + " sur o2: " +m.invoke(o2, null));
    System.out.println("Méthode " + m.getName() + " sur o: " +m.invoke(o, null));
                                
  } catch (SecurityException e) {
    e.printStackTrace();
  } catch (IllegalArgumentException e) {
    e.printStackTrace();
  } catch (ClassNotFoundException e) {
    e.printStackTrace();
  } catch (InstantiationException e) {
    e.printStackTrace();
  } catch (IllegalAccessException e) {
    e.printStackTrace();
  } catch (NoSuchMethodException e) {
    e.printStackTrace();
  } catch (InvocationTargetException e) {
    e.printStackTrace();
  }
}

Et le résultat en figure suivante.

Invocation dynamique de méthodes
Invocation dynamique de méthodes

Voilà : nous venons de créer deux instances d'une classe sans passer par l'opérateur new. Mieux encore, nous avons pu appeler une méthode de nos instances ! Je ne vais pas m'attarder sur ce sujet, mais gardez en tête que cette façon de faire, quoique très lourde, pourrait vous être utile.

Sachez que certains frameworks (ensemble d'objets offrant des fonctionnalités pour développer) utilisent la réflexivité afin d'instancier leurs objets (je pense notamment à des frameworks basés sur des fichiers de configuration XML tels que Hibernate, Struts, Spring…). L'utilité de ceci vous semble-t-elle évidente ? Même si vous ne savez pas les utiliser, peut-être avez-vous déjà entendu parler de ces frameworks Java.

Maintenant, vous n'allez pas utiliser ce genre de technique tous les jours. Cependant, il est possible que vous ayez besoin, pour une raison quelconque, de stocker le nom d'une classe Java dans une base de données afin, justement, de pouvoir l'utiliser plus tard. Dans ce cas, lorsque votre base de données vous fournira le nom de la classe en question, vous pourrez la manipuler dynamiquement.

  • Lorsque votre JVM interprète votre programme, elle crée automatiquement un objet Class pour chaque classe chargée.

  • Avec un tel objet, vous pouvez connaître absolument tout sur votre classe.

  • L'objet Class utilise des sous-objets tels que Method, Field et Constructor qui permettent de travailler avec vos différents objets ainsi qu'avec ceux présents dans Java.

  • Grâce à cet objet, vous pouvez créer des instances de vos classes Java sans utiliser new.

J'espère que cette partie vous a plu et que vous avez appris plein de bonne choses !
J'ai volontairement omis de parler des flux et des threads dans cette partie. Je préfère avoir des cas bien concrets à vous soumettre pour ça...

Bon : je sais que beaucoup d'entre vous l'attendent avec impatience, alors voici la partie sur la programmation événementielle !

Example of certificate of achievement
Example of certificate of achievement