Reflection

Présentation de l'API Reflection

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com

En PHP la conversion d'une chaîne de caractère en une méthode ou une variable est un jeu d'enfant. En Java ce traitement est plus complexe. Mais également on peut faire plus de choses. Tout cela grâce au package java.lang.reflect.

En gros, la réflexion est donc un concept qui permet au programme de s'auto-analyser. Elle rend également disponible la création d'un nouvel objet sans devoir passer par un constructeur.

Récupérer les informations sur une variable en Java

Comme on a déjà pu le constater dans l'article consacré aux types d'objets en Java, le développeur peut manipuler soit des objets, soit des types primitifs. Dans un autre article, cette fois-ci destiné à la portée des variables en Java, on a vu que les variables peuvent être accessibles à de différents niveaux. Toutes ces informations on peut facilement récupérer avec la refléxion, comme sur cet exemple :

afficher le code

Voici le résultat affiché :

Found field primitifBool. Field type is : boolean . Its generic type is : boolean. Field's visibility is : public static.
Field's value is true
New field's value is false
Found field objectList. Field type is : interface java.util.List . Its generic type is : java.util.List. Field's visibility is : public.
Field's value is [test]
New field's value is [test2]

On remarque la présence d'un tableau contenant les instances de la classe Field. Le tableau représente les champs trouvés dans une classe donnée. Ensuite on profite des méthodes disponibles qui permettent de récupérer toutes les informations nécessaires. On peut aller plus loin dans cette analyse et effecture des opérations de lecture et d'écriture sur ces objets. Elles se cachent sous get() et set(). A chaque fois il faut passer l'instance de la classe à laquelle appartient l'attribut en paramètre.

Récupérer les informations sur une méthode en Java

On peut effectuer les mêmes actions sur les méthodes présentes dans les classes. Notre classe Analyzer a été enrichie d'une nouvelle méthode analyzeMethods(), appelée depuis ReflectionExemple.main(). La classe de test contient également quelques fonctions supplémentaires utilisées lors de l'analyse :

afficher le code

Voici le résultat :

Found method : analyzeFields which returns void. Visibility is public. Parameters are :
Found method : analyzeMethods which returns void. Visibility is public. Parameters are :
Found method : outputTest which returns void. Visibility is private. Parameters are :
Found method : setToFalse which returns void. Visibility is private. Parameters are :
Found method : testWithParam which returns class java.lang.String. Visibility is private. Parameters are :
- parameter type is class java.lang.String and its generic parameter type class java.lang.String
Found method : toString which returns class java.lang.String. Visibility is public. Parameters are :
Calling method testWithParam(String t)
passed param is a
Invoke testWithParam method. Get a.

On voit que la récupération des informations s'effectue aussi facilement avec des getters primitifs. Cependant, l'appel d'une méthode depuis une chaîne de caractères est plus complexe. Tout d'abord il faut récupérer la méthode avec getDeclaredMethode(). Si elle prend des paramètres, il est nécessaire de les spécifier dans un tableau des paramètres. Ils doivent être instances de la classe Class. Une fois cette étape terminée, on passe à l'appel de la méthode. Cela s'effectue avec ivoke qui prend comme paramètre l'instance de la classe à laquelle appartient la méthode appelée. Le deuxième paramètre correspond à des paramètres pris par cette méthode. Ils doivent correspondre à des types spécifiés au moment de récupération de la méthode.

Récupérer les informations un constructeur en Java

Le constructeur est paradoxalement une méthode. Malgré cela, il n'est pas traité de la même manière dans le processus de réflexion. Il y a des fonctions spécifiques qui permettent de récupérer les informations sur les contructeurs disponibles pour des classes, comme dans cet exemple qui enrichit la classe Analyzer d'une nouvelle méthode analyzeConstructors() :

analyzer.analyzeConstructors();
// ...
    public Analyzer(String t) {
        this();
        System.out.println("Print value passed to constructor " + t);
    }

    public void analyzeConstructors() {
        Constructor[] constructors = thisClass.getDeclaredConstructors();
        for (Constructor constructor : constructors) {
            System.out.println("Found constructor " + constructor.getName() + " with visibility " + Modifier.toString(constructor.getModifiers()) + " and parameters : " );
            Class<?>[] paramTypes  ee= constructor.getParameterTypes();
            Type[] genParamTypes = constructor.getGenericParameterTypes();
            for (int i = 0; i < paramTypes.length; i++) {
                System.out.println("- " + paramTypes[i] + "(generic type : " + genParamTypes[i]+ ")") ;
            }
        }
        // create new Analyzer object
        try {
            Constructor c = thisClass.getDeclaredConstructor(String.class);
            c.setAccessible(true);
            Analyzer newAnalyzer = (Analyzer)c.newInstance(new String("b"));
        } catch (Exception eg) {
            System.out.println("An Exception was caught on creating new instance of Analyzer " + eg.getMessage());
        }
    }

Et le résultat :

Found constructor Analyzer with visibility public and parameters :
Found constructor Analyzer with visibility public and parameters :
- class java.lang.String(generic type : class java.lang.String)
Print value passed to constructor b

L'analyse des constructeurs ne se différencie pas beaucoup de celle des méthodes. On récupère les informations sur les paramètres quasiment de la même manière. Une grosse différence repose sur la création d'une nouvelle instance. Au tout début on récupère le constructeur correspondant avec la méthode getDeclaredConstructor(). L'objet récupéré doit ensuite être déclaré accessible par la fonction setAccessible(). Si cette méthode prend comme paramètre true, cela veut dire que Java ne va pas vérifier son accessibilité lors de l'appel. Si la valeur était false, Java analyserait la possibilité d'accès et pourrait générer une exception SecurityException. Ensuite, dans le block try{} catch{}, la création d'une nouvelle instance se déroule avec la méthode newInstance() de la classe Constructor qui accepte des paramètres en fonction du constructeur utilisé.

Il faut garder en tête qu'une instance peut être initialisée avec la méthode newInstance() de la classe Classe. Contrairement à la même fonction de la classe Constructor, utilisée dans notre exemple, elle n'accepte pas de paramètres. Voici l'exemple de son implémentation dans analyzeConstructors :

    try {
      Analyzer analyzerFromClass = (Analyzer) thisClass.newInstance();
      System.out.println("Created Analyzer instance : " + analyzerFromClass.toString());
    } catch (Exception e) {
      System.out.println("An Exception was caught on creating new instance of Analyzer with Class.newInstance " + e.getMessage());
    }

Le résultat est le suivant :

Created Analyzer instance Analyzer

Si l'on essayeait de créer une nouvelle instance avec le constructeur Analyzer(String t)), on recevrait une erreur de compilation (comme quoi Class.newInstance() ne permettrait pas d'appeler les constructeurs avec des paramètres) :

ReflectionExemple.java:147: newInstance() in java.lang.Class cannot be applied to (java.lang.String)
      Analyzer analyzerFromClass = (Analyzer)thisClass.newInstance(new String("c"));
Bartosz KONIECZNY Packages

Une question ? Une remarque ?

*

*

Un conseil Zend Framework

Comment faciliter le travail entre notre application et l'Ajax ?

L'un des moyens est l'utilisation du ContextSwitch. Il permet de définir le type de réponse renvoyé.