Java – Exemple de réflexivité dynamique

Comme bien des langages OO, Java permet aux programmes de réaliser la réflexivité dynamique, soit la représentation des entités du programme à même le programme.

Pour le tronçon impliquant de la réflexivité, nous aurons recours aux classes et outils du paquetage java.lang.reflect.

import java.lang.reflect.*;

Nous utiliserons une petite classe, somme toute simple mais avec une (courte) gamme de services incluant méthodes de classe, méthodes d'instance, attributs, constructeurs, membres privés et publics, et même un risque de levée d'exception.

La classe ValeurInvalideException sera le mécanisme utilisé pour souligner qu'une valeur utilisée dans le programme n'est pas convenable.

class ValeurInvalideException extends Exception {
   private int valeur;
   public ValeurInvalideException(int val) {
      valeur = val;
   }
   public String getMessage() {
      return "Valeur invalide: " + valeur;
   }
}

J'utiliserai une classe X, banale, qui permet d'entreposer un entier positif, de même que ses quelques services, pour les besoins de l'exposé.

class X {
   private int val_;
   private static boolean estValeurValide(int val) {
      return val >= 0;
   }
   private void setValue(int val) throws ValeurInvalideException {
      if (!estValeurValide(val)) {
         throw new ValeurInvalideException(val);
      }
      val_ = val;
   }
   public X (int val) throws ValeurInvalideException {
      setValue(val);
   }
   public int getValue() {
      return val_;
   }  
}

Le code utilisé en exemple sera logé dans la classe Réflexivité, et se subdivisera en deux méthodes de classe nommées respectivement testSansRéflexivité() et testAvecRéflexivité(). Ces deux méthodes feront le même travail, mais avec réflexivité ou sans réflexivité selon le cas.

Les paramètres reçus au lancement de Réflexivité.main() sont relayés aux deux fonctions de test, qui essaieront de les transformer en int dans l'optique d'instancier divers X.

public class Réflexivité {
   public static void main(String ... args) {
      testSansRéflexivité(args);
      testAvecRéflexivité(args);
   }

La version sans réflexivité prend plusieurs String, les convertit une à une en int pour instancier un X (ce qui est susceptible de lever une exception si le int ne convient pas aux règles d'affaires d'un X) puis affiche la valeur du X résultant.

La fonction se termine lorsque tous les paramètres ont été traités, ou lors de la première levée d'exception qui y est constatée, que cette-dernière soit levée par l'instanciation d'un X ou par la tentative de conversion d'une String en int.

// ...
   private static void testSansRéflexivité(String ... args) {
      System.out.println("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-");
      System.out.println("Test sans recours a la reflexivite");
      try {
         for (String s : args) {
            X x = new X(Integer.parseInt(s));
            System.out.println("X construit, valeur: " + x.getValue());
         }
      } catch (ValeurInvalideException vie) {
         System.err.println("X non-construit, raison --> " + vie.getMessage());
      } catch (NumberFormatException nfe) {
         System.err.println("Erreur de formatage --> " + nfe.getMessage());
      }
   }

La version avec réflexivité fait la même chose, mais en passant par les mécanismes plus dynamiques, plus flexibles, mais aussi plus complexes et plus lents que Java met à la disposition des programmeuses et des programmeurs. Ainsi, pour créer un:

  • Nous obtenons une référence c sur une instance de la class Class représentant le cas particulier de la classe X, et nous faisons cela par son nom (appel à Class.forName("X"))
  • À travers c, nous obtenons un constructeur que nous nommons ctor, donc une instance du type Constructor, représentant le constructeur bien spécifique acceptant un int en paramètre (paramètre int.class). Nous aurions aussi pu demander la liste des constructeurs et itérer à travers cette liste pour trouver celui qui nous sied
  • Pour instancier un X, nous appelons ensuite ctor.newInstance() en lui passant le paramètre souhaité. Le X résultant est un X tout ce qu'il y a de plus normal

Remarquez que les cas d'exceptions possibles sont bien plus nombreux ici : puisque nous faisons manuellement ce qui aurait pu être fait (et validé) par les mécanismes plus directs, il y a dans notre code plus d'aspects risqués.

L'exception ValeurInvalideException n'apparaît pas dans la liste, toutefois, car les mécanismes de Java ne sont pas Exception-Neutral. Si une exception est levée par l'appel à newInstance(), cette exception sera consommée par la fonction et masquée par une exception plus générique (voir l'exemple ci-dessous).

// ...
   @SuppressWarnings("rawtypes")
   private static void testAvecRéflexivité(String ... args) {
      System.out.println("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-");
      System.out.println("Test avec recours a la reflexivite");
      try {
         for (String s : args) {
            Class c = Class.forName("X");
            Constructor ctor = c.getConstructor(int.class); // ou c.getConstructors() puis on itère
            X x = (X) ctor.newInstance(Integer.parseInt(s));
            System.out.println("X construit, valeur: " + x.getValue());
         }
//      } catch (ValeurInvalideException vie) {
//         System.err.println("X non-construit, raison --> " + vie.getMessage());
      } catch (NumberFormatException nfe) {
         System.err.println("Erreur de formatage --> " + nfe.getMessage());
      } catch (ClassNotFoundException cnfe) {
         System.err.println("Oups, suspect --> " + cnfe.getMessage());
      } catch (InstantiationException ie) {
         System.err.println("Oups, suspect --> " + ie.getMessage());
      } catch (IllegalAccessException iae) {
         System.err.println("Oups, suspect --> " + iae.getMessage());
      } catch (InvocationTargetException ite) {
         System.err.println("Oups, suspect --> " + ite.getMessage());
      } catch (NoSuchMethodException nsme) {
         System.err.println("Oups, suspect --> " + nsme.getMessage());
      } catch (SecurityException se) {
         System.err.println("Oups, suspect --> " + se.getMessage());
      }
   }
}

Une exécution possible de ce programme, invoqué comme suit :

java Réflexivité 3 7 0 -8

... serait la suivante :

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
Test sans recours a la reflexivite
X construit, valeur: 3
X construit, valeur: 7
X construit, valeur: 0
X non-construit, raison --> Valeur invalide: -8
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
Test avec recours a la reflexivite
X construit, valeur: 3
X construit, valeur: 7
X construit, valeur: 0
class java.lang.reflect.InvocationTargetException... --> null

Lectures complémentaires

Quelques liens supplémentaires pour enrichir le propos.


Valid XHTML 1.0 Transitional

CSS Valide !