dimanche 15 mars 2015

Java et la réflexion [1/4]

La reflexion en Java est un élément essentiel à connaitre. Ce n'est pas difficile en soit, mais certaines parties peuvent paraître un peu obscure.
A quoi la reflexion peut-elle servir ?
Par exemple, à faire un mapping d'objet facilement.
Prenons l'exemple de MongoDB. Actuellement le projet MongoJack qui permet de mapper les objets MongoDb en Java ne propose pas le support du driver MongoDb 3.
Nous allons donc écrire un petit mapper pour le driver MongoDb 3. L'exemple ne prendra pas en compte tous les types, seulement ceux intéressants dans le cadre d'un apprentissage.
Les base de la reflexion
La réflexion permet de découvrir les caractéristiques d'une classe (attributs, méthodes, annotation...). Ceci est très pratique pour faire un traitement générique. Dans notre cas, découvrir les getter pour créer un object Document du driver version 3 de MongoDb.
Les attributs
Récupération des attributs publics
Afin de récupérer les attributs publics d'une classe, il faut utiliser la méthode getFields(). Cette méthode ne retourne pas les attributs privés !
Vous récupérez un object Field sur lequel vous pourrez utiliser la méthode get() pour récupérer la valeur et set() pour modifier la valeur.

Exemple :
import java.lang.reflect.Field;

public class TestField {
    public String field1;
    private String field2;

    public TestField(String f1, String f2) {
        this.field1 = f1;
        this.field2 = f2;
    }

    public String getField2() {
        return this.field2;
    }

    public static void main(String[] args) throws IllegalAccessException {
        // Le type est volontairement objet pour prouver que même si on
        // ne connait pas le type, il est possible d'avoir les informations.
        Object o = new TestField("1", "2");

        // On ne connait pas l'objet mais tout object à getClass() qui retourne
        // la classe. De là on récupère les attributs public.
        Field[] fields = o.getClass().getFields();

        // On itère sur les attributs public
        for (Field f : fields) {
            System.out.println("Name : " + f.getName());
            System.out.println("Old Value : " + f.get(o));

            f.set(o, "9");
        }

        System.out.println("New Value : " + ((TestField) o).field1);
    }
}

Voici le résultat :
Name : field1
Old Value : 1
New Value : 9

On voit que le champ a bien été modifié.
Récupération des attributs privés
Pour récupérer les attributs privés, il faut utiliser la méthode getDeclaredFields() au lieu de getFields(). Toutefois, il faut, avant d'appeler la méthode get() rendre le champs accessible par setAccessible(true). Voici l'exemple :
        Field[] fields = o.getClass().getDeclaredFields();

        // On itère sur les attributs public
        for (Field f : fields) {
            // Rend le champs accessible
            f.setAccessible(true);
            
            System.out.println("Name : " + f.getName());
            System.out.println("Old Value : " + f.get(o));

            f.set(o, "9");
        }

Il faut noter que getFields() ou getDeclaredFields() ne retourne pas les attributs hérités. Pour cela il faudra utiliser la classe mère en la récupérant via getSuperclass().
Détecter si l'attribut est de type primitif
Pour savoir si un attribut est de type primitif et donc à ne pas instancier, il faut utiliser f.getType().isPrimitive(). Après, pour savoir de quel primitif il s'agit, il faut comparer le type avec le type primitif Boolean.TYPE, Byte.TYPE...
Détecter si l'attribut est de type tableau
Même chose avec les tableaux qu'avec les primitifs, il faut utiliser f.getType().isArray().
Détecter si l'attribut est de type List
Pour savoir si un attribut est de type List (ou autre), il faut simplement comparer le type avec la classe liste : f.getType() == List.class.
Récupération du générique de la liste
Pour savoir s'il s'agit d'un générique, il faut vérifier que le type renvoyé par f.getGenericType() est de type ParameterizedTypeImpl.
Exemple :
if (f.getGenericType() instanceof ParameterizedTypeImpl) {
    System.out.println(
            ((ParameterizedTypeImpl) f.getGenericType()).getActualTypeArguments()[0].getTypeName());
} 
Ensuite, il faut récupérer les arguments des types (getActualTypeArguments). Il s'agit d'une table. Dans le cas d'une liste, il n'y en a qu'un élément. Dans le cas d'une Map, il y en aura 2.

Aucun commentaire:

Enregistrer un commentaire