Plan du site  
pixel
pixel

Articles - Étudiants SUPINFO

Introduction à l'introspection / réflexion en JAVA

Par Bruno LARIBIERE Publié le 14/05/2019 à 13:03:42 Noter cet article:
(0 votes)
Avis favorable du comité de lecture

La réflexion est un procédé qui permet à un programme de consulter et de modifier ses fonctionnalités internes voir même celles d'autres processus.

Pour faire plus simple, c'est la capacité que peut avoir du code à pouvoir accéder à son propre code.

Avant propos:

  • Pour pouvoir assimiler la notion de réflexion (que ce soit en java ou tout autre langage permettant l'introspection) vous devez maîtriser la programmation orientée objet.

  • Pour la dernière partie vous devez être familiarisé avec la sérialisation d'objet en format JSON.

Dans un premier temps nous allons voir comment un développeur va pouvoir créer du code générique en se basant sur la réflexion afin d'accélérer et simplifier l'intégration de fonctions pour d'autres développeurs.

Nous allons aussi voir dans un second temps comment accéder à des variables protégées et comment en modifier leurs valeurs.

Puis nous terminerons par voir ensemble comment la réflexion peut vous aider dans la sérialisation et désérialisation de vos objets.

Accéder dynamiquement à un ensemble de classe étendue d'une interface

Pour comprendre l'intérêt de la réflexion, il faut comprendre quel problème elle est amenée à résoudre. Pour vous aider à y voir plus clair nous allons prendre un cas concret.

Le problème

Prérequis:

  • Un fichier .java dans un package (ici com.supinfo)

  • L'ajout de la dépendance Guava (librairie de fonctions google pour java https://github.com/google/guava ==> elle nous aidera à condenser notre code)

Votre programme dispose d'un ensemble d'objet de type animaux: Chien, Chat et Licorne.

Chaque animal est un objet différent, chacun étendu d'un objet de type animal. Chaque animal a la possibilité de "crier" et nous aimerions faire crier tous les animaux dont l'on dispose.

Plusieurs manière sont possibles:

  • Créer une liste d'animaux

  • Créer une map d'animaux

  • Créer des enums d'animaux etc ...

Prenons l'exemple le plus simple. Nous générons ainsi le code ci dessous:

Toutes les classes sont ici codées dans le même sujet à des fins de visibilité, sachez que ce code est bien évidemment valable si les classes sont dans des fichiers distincts.

package com.supinfo;

public class Main {

    public static void main(String[] args) {
        ArrayList<Animal> animaux = new ArrayList<>(Arrays.asList(new Chien(), new Chat(), new Licorne()));
        for(Animal a : animaux)
        {
            a.crier();
        }
    }
}

interface Animal{

    void crier();
}

class Chien implements Animal{

    @Override
    public void crier() {
        System.out.println("Waf waf");
    }
}

class Chat implements Animal{
    @Override
    public void crier() {
        System.out.println("Miaou Miaou");
    }
}

class Licorne implements Animal{
    @Override
    public void crier() {
        System.out.println("Rainbowwww");
    }
}

Après execution nous obtenons le retour suivant

Waf waf
Miaou Miaou
Rainbowwww

Parfait, nous avons un code fonctionnel mais ... peu maintenable. En effet si demain nous venions à créer un nouvel animal, comme un poisson, nous serions obligé de créer la classe mais surtout d'ajouter notre poisson à la liste des animaux de notre Main.

Si nous devons ajouter des milliers d'animaux, les problèmes majeurs seront:

  • Ajouter chaque animal à l'ArrayList, sans en oublier,

  • Si des changements sont faits sur un objet animal, tel qu'un renommage, il faudra répercuter ce changement dans l'ArrayList

Nous allons à présent voir comment la réflexion peut nous aider à régler dynamiquement ce problème.

La solution par réflexion

L'objectif est de retrouver tous les animaux de manière dynamique sans jamais avoir à les ajouter à la main.

Dans notre main:

    public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {

        //Nous récupérons le classLoader qui contient l'ensemble des classes chargées par notre instance Java, TOUTES les classes sont ainsi chargées
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

        //C'est ici que Guava nous simplifie la vie, il récupère toutes les classes qui sont dans le package com.supinfo, c'est une étape de la reflexion
        ImmutableSet<ClassInfo> classes = ClassPath.from(classLoader).getTopLevelClasses("com.supinfo");

        //Nous bouclons sur chaque métadata des classes trouvées (soit: Main, Animal, Chien, Chat et Licorne)
        for(ClassInfo classInfo : classes)
        {
            //Grâce à cette métadata nous pouvons obtenir le nom de la class. Grâce à la réflexion, on peut transformer ce nom de classe en objet de type Class
            Class targetClass = Class.forName(classInfo.getName());

            //Puis nous vérifions que la classe obtenue puisse être instancier depuis la classe Animal et que cette dernier ne soit pas elle même une interface
            //Nous ne récupérons pas l'interface car elle réprésente un animal, mais n'est pas un animal en soit.
            //La liste aura donc pour valeur : Chien, Chat et Licorne
            if(Animal.class.isAssignableFrom(targetClass) && !targetClass.isInterface())
            {
                //Une autre force de la reflexion est de pouvoir instancier dynamiquement une classe sans avoir à préciser son type.
                //Cette ligne serait l'équivalent d'un new Chien() par exemple
                Object monInstance = targetClass.newInstance();

                //Nous pouvons caster cette nouvelle instance en Animal puisque la classe est intansiable depuis animal, ceci a été vérifié dans le if ci dessus.
                Animal monAnimal = (Animal) monInstance;

                //Pour terminer nous pouvons enfin appeler notre fonction crier
                monAnimal.crier();
            }
        }
    }

Résultat de la sortie:

Miaou Miaou
Waf waf
Rainbowwww

Grâce à cette implémentation n'importe quel développeur qui ajouterai une nouvelle classe implémentant Animal, sera automatiquement inclus à notre boucle et verra sa fonction .crier() appelée.

Bibliographie

Accès aux variables via réflexion

En plus de pouvoir accéder aux classes la réflexion vous permet d'accéder aux fields (appelés champs en français) contenue dans une classe. La réflexion vous permet de consulter et assigner n'importe quel fields dans un programme. Nous allons voir ensemble un exemple de cela.

Ces méthodes n'ont besoin d'aucune dépendance !

Le problème

Nous arrivons sur un programme déjà existant qui a une librairie d'objet en dépendance. Cette librairie contient un objet de type Maison définie ainsi:

class Maison{
    private int nombreEtage = 2;
    public String adresse = "France";

    public void setEtage(int etage)
    {
        if(etage>2)
            this.nombreEtage = 2;
        else
            this.nombreEtage = etage;
    }
}

Nous ne pouvons pas modifier cette classe puisqu'elle est dans une librairie.

Dans notre programme principale voici ce qu'un développeur à marqué:

public class Injection {

    public static void main(String[] args) {
        Maison m = new Maison();
        System.out.println(m.adresse);
    }
}

La sortie obtenue est donc:

France

Vous avez reçu les objectifs suivants:

  • Ne pas modifier l'objet maison

  • Ne pas modifier la méthode d'instanciation de maison dans le main()

  • Afficher le nombre d'étage de la maison

  • Réussir à mettre le nombre d'étage de la maison à 8

Vous comprenez alors rapidement que l'objectif ne sera pas facile à atteindre. En effet le field (champ) "nombreEtage" est "private" il vous est donc à priori impossible d'y accéder.

Nous allons cependant voir étape par étape qu'il est tout à fait possible et facile d'atteindre ces objectifs via la réflexion.

Accéder à une variable privée

Comme dit en introduction nous allons voir que la réflexion peut parfaitement accéder dynamiquement à l'ensemble des fields d'une class et ce de manière très simple. Nous allons le vérifier avec l'exemple suivant.

    public static void main(String[] args) {
        for (Field f : Maison.class.getDeclaredFields())
        {
            System.out.println(f.getName());
        }
    }

Nous affichera la sortie suivante:

nombreEtage
adresse

Nous observons à ce stade que la fonction getDeclaredFields() nous a permis de retourner tous les fields de la classe maison (même celui déclaré privé). Et pour chaque fields nous affichons son nom.

Travaillons à présent sur une implémentation afin d'atteindre notre premier objectif, celui d'afficher le contenu de la variable privée nombreEtage.

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Maison m = new Maison();
        System.out.println(m.adresse);

        //Au lieu de retourner tous les fields, nous prenons uniquement celui qui nous interesse, 'nombreEtage'
        Field fieldNombreEtage = Maison.class.getDeclaredField("nombreEtage");

        //Nous récupérons la valeur du champ (voir détail plus bas)
        Object object = fieldNombreEtage.get(m);

        //Nous affichons la valeur du champ
        System.out.println(object);
    }

Quelques explications s'impose.

Nous avons grâce à l'appel Maison.class.getDeclaredField("nombreEtage") récupéré le champ nombreEtage. Il est essentiel à ce stade là de comprendre que nous avons récupéré un champ et non la valeur du champ

C'est ce point qui peut parfois être difficile à appréhender lors de l'apprentissage de la réflexion.

Pour faire un parralèle, imaginez une feuille de papier où il y aurait une case à l'intérieur de laquelle il y aurait écrit "helloworld". Si l'on vous demande "que représente la case de ce genre de feuille" vous répondrez "C'est un champ dans lequel on peut écrire quelquechose". C'est ce qu'un Field représente. Il représente une variable, mais pas sa valeur.

Si vous avez compris cela, vous pourrez comprendre l'étape suivante: Object object = fieldNombreEtage.get(m);

C'est cette étape qui nous permet d'obtenir la valeur du champ fieldNombreEtage dans le contexte de l'objet m

Pour continuer sur l'exemple de notre feuille de papier, si comme si l'on vous demandait "quel est la valeur de la case sur cette feuille de papier" vous répondrez "helloworld". Il faut comprendre à présent que pour obtenir la valeur d'un champ vous devez savoir pour quel objet on vous le demande.

Ainsi le paramètre m est l'objet de type Maison pour lequel on demande la valeur du champ.

Exécutons notre programme à présent :

France
Exception in thread "main" java.lang.IllegalAccessException: 
Class com.supinfo.Injection can not access a member of class com.supinfo.Maison with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
at java.lang.reflect.Field.get(Field.java:390)
at com.supinfo.Injection.main(Injection.java:14)

Sans la réflexion, si vous aviez tenté d'accéder à nombreEtage vous auriez eu une erreur du compilateur vous indiquant que la variable n'est pas accessible car elle est privée. C'est exactement ce qui se passe ici aussi après compilation. Heureusement la réflexion permet non seulement d'obtenir la valeur des champs, mais aussi de modifier les paramètres propre à lui. Nous allons ainsi faire en sorte de transformer ce champs privé en publique:

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Maison m = new Maison();
        System.out.println(m.adresse);

        //Au lieu de retourner tous les fields, nous prenons uniquement celui qui nous interesse, 'nombreEtage'
        Field fieldNombreEtage = Maison.class.getDeclaredField("nombreEtage");

        //Nous forcons le champs à passer de private à public
        fieldNombreEtage.setAccessible(true);

        //Nous récupérons la valeur du champ (voir détail plus bas)
        Object object = fieldNombreEtage.get(m);

        //Nous affichons la valeur du champ
        System.out.println(object);
    }

Et la sortie est:

France
2

Ce qui est bien ce que nous attendons par rapport à notre objet maison qui prenait une valeur de 2 par défaut pour la variable nombreEtage. Premier objectif réussi.

Assigner une variable privée grâce à la réflexion

Notre objectif est à présent d'assigner la variable nombreEtage à 8, problème le code nous en empêche:

    public void setEtage(int etage)
    {
        if(etage>2)
            this.nombreEtage = 2;
        else
            this.nombreEtage = etage;
    }

Si jamais nous tentons d'appeler setEtage(8) une vérification sera faite et le nombre d'étage assigné sera à 2. La variable est, comme je le rappelle, private, une assignation classique n'est donc pas possible.

Cependant, nous allons bypasser la fonction setEtage et allons utiliser la réflexion pour forcer l'assignation de la variable. Nous venons de voir que nous avions grâce à la réflexion transformé au cours de l'exécution du code une variable private en public. Et en tenant compte qu'au moment de l'exécution cette variable est publique, nous allons pouvoir l'assigner facilement comme ceci:

    fieldNombreEtage.setAccessible(true);

    fieldNombreEtage.set(m,8);

et la sortie associée à ce code devient:

France
8

Le principe de cette fonction et donc de prendre un champ (passé en accessible au préalable si il était private). Et par rapport à un objet (ici m) lui assigner une valeur en mémoire (ici 8)

On assigne alors à la variable une valeur sans passer par un constructeur ou un setter.

Utiliser la réflexion dans la sérialisation d'objet

Nous allons à présent voir une utilisation plus fréquente de la réflexion . Dans le cadre du stockage ou transfert de donnée, la sérialisation est souvent une solution fréquemment employée. Pour rappel, la sérialisation est le fait de transformer une information en une suite d'informations plus petite à des fins de stockage ou d'échange d'informations.

Nous allons parler de sérialisation et de désérialisation sous format JSON. Pour les exemples qui vont suivre nous utiliserons la bibliothèque Gson. Cependant n'importe qu'elle autre librairie supporte le type d'utilisation que nous allons employer

Le problème

Il arrive certaine fois que vous souhaitiez sérialiser différents type d'objets. Le problème est d'arriver à identifier l'objet d'origine une fois qu'il a été sérialisé. Exemple:

public class Serialisation {

    public static void main(String[] args)
    {
        //Nous instancions nos deux objets
        Chien chien = new Chien("Medor","Waff waff !");
        Chat chat = new Chat("Shampo", "Miaou miaou !");

        //Nous sérialisons les deux objets puis nous les affichons
        String chienJson = new GsonBuilder().setPrettyPrinting().create().toJson(chien);
        String chatJson = new GsonBuilder().setPrettyPrinting().create().toJson(chat);

        System.out.println(chienJson);
        System.out.println("\n--------------\n");
        System.out.println(chatJson);

    }
}

class Animal{
    String surnom;

    public Animal(String surnom)
    {
        this.surnom = surnom;
    }
}

class Chien extends Animal{

    String aboiment;

    public Chien(String surnom, String aboiment) {
        super(surnom);
        this.aboiment = aboiment;
    }
}

class Chat extends Animal{

    String miaulement;

    public Chat(String surnom, String miaulement) {
        super(surnom);
        this.miaulement = miaulement;
    }
}

Nous avons dans ce cas instancié deux objets de types animaux, et nous les avons sérialisé, voici sans surprise le résultat obtenu:

{
  "aboiment": "Waff waff !",
  "surnom": "Medor"
}

--------------

{
  "miaulement": "Miaou miaou !",
  "surnom": "Shampo"
}

On vous demande alors d'atteindre les objectifs suivants:

  • L'ensemble des Jsons obtenus doivent être mis dans une liste de String, comme si nous souhaitions sauvegarder toutes les datas au même endroit.

  • L'objet désérialisé doit être le même que celui sérialisé ( donc si on sérialise un Chat on veut obtenir un Chat, et non un Animal).

  • On souhaite que le code puisse évoluer, ainsi, si un autre développeur ajoute d'autres animaux, il ne doit pas avoir besoin de modifier le système de sérialisation et désérialisation ni même le comprendre ! Tout doit se faire de manière quasi invisible pour lui.

Les objectifs ci-dessus peuvent paraître complexe à atteindre, cependant nous allons voir que la réflexion à toute sa place pour répondre à cette problématique. Nous allons voir ensemble une méthode très simplifiée et compréhensible pour répondre à ces problématiques.

Rappel, la désérialisation classique

Pour rappel, je vous fourni le code d'une désérilisation classique, et qui ne peut pas répondre aux objectifs fixés

   public static void main(String[] args)
    {
        //Nous instancions nos deux objets
        Chien chien = new Chien("Medor","Waff waff !");
        Chat chat = new Chat("Shampo", "Miaou miaou !");

        //Nous sérialisons les deux objets puis nous les affichons
        String chienJson = new GsonBuilder().setPrettyPrinting().create().toJson(chien);
        String chatJson = new GsonBuilder().setPrettyPrinting().create().toJson(chat);

        //Nous transformons les json en objet, c'est la désérialisation
        Object object1 = new Gson().fromJson(chienJson,Chien.class);
        Object object2 = new Gson().fromJson(chatJson,Chat.class);

        //Nous affichons les résultats obtenus
        System.out.println("L'objet1 est de type: "+object1.getClass());
        System.out.println("L'objet2 est de type: "+object2.getClass());
    }

Nous donne la sortie:

L'objet1 est de type: class com.supinfobis.Chien
L'objet2 est de type: class com.supinfobis.Chat

Ceci est donc un exemple de désérialisation classique, le problème principal est que l'on est obligé de connaître à l'avance le contenu du json pour savoir qu'elle classe utiliser pour le désérialiser.

Désérialisation autonome en JSON grâce à la réflexion

Identification du JSON

Le premier problème est due au fait que nous devons être en capacité de savoir grâce au Json, le type d'objet qui a été sérialisé. Notre première tâche sera donc d'ajouter cette information dynamiquement à nos objets JSON.

Comme l'objectif est que l'ajout de nouveaux animaux soit le plus simple possible pour un autre développeur, nous allons effectuer cet ajout dans la classe Animal, qui impactera donc tous les classe filles (Chat, Chien etc ....)

public class Serialisation {

    public static void main(String[] args)
    {
        //Nous instancions nos deux objets
        Chien chien = new Chien("Medor","Waff waff !");
        Chat chat = new Chat("Shampo", "Miaou miaou !");

        //Nous sérialisons les deux objets puis nous les affichons
        String chienJson = new GsonBuilder().setPrettyPrinting().create().toJson(chien);
        String chatJson = new GsonBuilder().setPrettyPrinting().create().toJson(chat);

        System.out.println(chienJson);
        System.out.println(chatJson);
    }
}

abstract class Animal{
    String surnom;
    String classeOrigine;

    public Animal(String surnom)
    {
        this.surnom = surnom;
        this.classeOrigine= this.getClass().getName();
    }
}

Comme vous le voyez, seul le constructeur de la classe Animal a changé. Nous ne faisons que stocker la classe d'origine dans une String à chaque fois qu'un animal est créé. Nous avons donc à présent le retour suivant:

{
  "aboiment": "Waff waff !",
  "surnom": "Medor",
  "classeOrigine": "class com.supinfobis.Chien"
}
{
  "miaulement": "Miaou miaou !",
  "surnom": "Shampo",
  "classeOrigine": "class com.supinfobis.Chat"
}
Ajout d'un désérialiseur personnalisé

Nous allons voir ensemble une première tentative de désérialisation sans préciser de type de sortie pour vous montrer le résultat que l'on ne veut pas obtenir:

 public static void main(String[] args)
    {
        //Nous factorison gson, et centralison tout dans cette variable
        Gson gson = new GsonBuilder().setPrettyPrinting().create();

        //Nous créons une liste de String dans laquelle nous ajoutons chacun de nos objets sérialisés.
        ArrayList<String> objetsJson = new ArrayList<String>();
        objetsJson.add(gson.toJson(new Chien("Medor","Waff waff !")));
        objetsJson.add(gson.toJson(new Chat("Shampo", "Miaou miaou !")));

        //Nous affichons chacuns des objets contenus dans la liste
        System.out.println(" -----------------");
        System.out.println("Deserialisation des objets ==> :");
        System.out.println(" -----------------");

        for(String objet : objetsJson)
        {
            System.out.println("Serialisation d'origine ==>");
            System.out.println(objet);

            Object objectInconnu = new Gson().fromJson(objet,Animal.class);

            System.out.println("Valeur de l'objet désérialisé ==> "+objectInconnu.getClass());
        }
    }

Et nous obtenons en sortie:

 -----------------
Deserialisation des objets ==> :
 -----------------
Serialisation d'origine ==>
{
  "aboiment": "Waff waff !",
  "surnom": "Medor",
  "classeOrigine": "class com.supinfobis.Chien"
}
Valeur de l'objet deserialise ==> class com.supinfobis.Animal
Serialisation d'origine ==>
{
  "miaulement": "Miaou miaou !",
  "surnom": "Shampo",
  "classeOrigine": "class com.supinfobis.Chat"
}
Valeur de l'objet deserialise ==> class com.supinfobis.Animal

Nous avons donc en sortie de deserialisation un objet de type Animal, alors que nous voudrions un Chat ou un Chien en fonction du JSON. Le problème c'est que en procédant ainsi nous avons perdu les informations spécifiques aux objets Chat et chien.

Nous allons à présent ajouter un désérialiseur personnalisé. Ce dernier va s'intégrer à notre processus de désérialisation, et va appliquer des opérations sur chaque objet à désérialiser. Nous allons modifier le Gson en lui demandant d'utiliser un désérialiseur personnalisé à chaque fois que l'on voudra désérialiser un objet de type animal.

Gson gson = new GsonBuilder().setPrettyPrinting().registerTypeAdapter(Animal.class,new DeserialiserPersonnalise()).create();

Puis nous allons créer notre désérialiseur personnalisé:

class DeserialiserPersonnalise implements JsonDeserializer<Animal>
{
    @Override
    public Animal deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        //Nous savons par avance que c'est un jsonObject qui nous sera donné en entré.
        JsonObject jsonObject = (JsonObject) json;

        //Nous récupérons la valeur du champ classeOrigine directement dans le json, elle contient par exemple la valeur com.supinfobis.chat ou com.supinfobis.chien
        String classeOrigine = jsonObject.get("classeOrigine").getAsString();


        try {
            //Nous utilison la reflexion pour convertir dynamiquement la chaine de caractère obtenue en objet de type Class.
            Class classe = Class.forName(classeOrigine);

            //Nous avons obtenu grâce à la reflexion, la classe de l'objet à désérialiser. On utilise cette classe en tant que paramètre
            //de désérialisation, comme pour une désérialisation classique.
            return context.deserialize(json,classe);

        } catch (ClassNotFoundException e) {
            //En cas d'erreur nous affichons la classe non retrouvée
            System.out.println("Classe non retrouvee: "+classeOrigine);
        }

        return null;
    }
}

Ce désérialiseur sera donc utilisé dès qu'un développeur voudra désérialiser un objet de type animal.

Quelques informations supplémentaires, l'objet JsonElement contient le Json à désérialiser, et JsonDeserializationContext contient le Gson() que nous avons instancié au début du programme.

Voici la sortie obtenue avec ce code:

 -----------------
Deserialisation des objets ==> :
 -----------------
Serialisation d'origine ==>
{
  "aboiment": "Waff waff !",
  "surnom": "Medor",
  "classeOrigine": "com.supinfobis.Chien"
}
Valeur de l'objet deserialise ==> class com.supinfobis.Chien
Serialisation d'origine ==>
{
  "miaulement": "Miaou miaou !",
  "surnom": "Shampo",
  "classeOrigine": "com.supinfobis.Chat"
}
Valeur de l'objet deserialise ==> class com.supinfobis.Chat

Il ne reste plus qu'à caster l'objet pour accéder si vous le souhaitez aux champs propres à chaque classe.

Un développeur peu à présent ajouter n'importe quel type d'animal dans le programme, la désérialisation se fera automatiquement du moment que la désérialisation appelle bien DeserialiserPersonnalise en dans que déséraliser spécifique aux objets animaux.

Vous savez à présent comment désérialiser des objets qui ne sont pas du même type à partir d'un format Json, via la réflexion.

Voici le programme complet (sans les commentaires) que vous pouvez tester chez vous.

package com.supinfobis;

import com.google.gson.*;

import java.lang.reflect.Type;
import java.util.ArrayList;


public class Serialisation {

    public static void main(String[] args)
    {
        Gson gson = new GsonBuilder().setPrettyPrinting().registerTypeAdapter(Animal.class,new DeserialiserPersonnalise()).create();

        ArrayList<String> objetsJson = new ArrayList<String>();
        objetsJson.add(gson.toJson(new Chien("Medor","Waff waff !")));
        objetsJson.add(gson.toJson(new Chat("Shampo", "Miaou miaou !")));

        System.out.println(" -----------------");
        System.out.println("Deserialisation des objets ==> :");
        System.out.println(" -----------------");

        for(String objet : objetsJson)
        {
            System.out.println("Serialisation d'origine ==>");
            System.out.println(objet);
            Object objectInconnu = gson.fromJson(objet,Animal.class);
            System.out.println("Valeur de l'objet deserialise ==> "+objectInconnu.getClass());
        }
    }
}

class DeserialiserPersonnalise implements JsonDeserializer<Animal>
{
    @Override
    public Animal deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

        JsonObject jsonObject = (JsonObject) json;
        String classeOrigine = jsonObject.get("classeOrigine").getAsString();

        try {
            Class classe = Class.forName(classeOrigine);
            return context.deserialize(json,classe);
        } catch (ClassNotFoundException e) {
            System.out.println("Classe non retrouvee: "+classeOrigine);
        }
        return null;
    }
}

class Animal{
    String surnom;
    String classeOrigine;

    public Animal(String surnom)
    {
        this.surnom = surnom;
        this.classeOrigine= this.getClass().getName();
    }
}

class Chien extends Animal{

    String aboiment;

    public Chien(String surnom, String aboiment) {
        super(surnom);
        this.aboiment = aboiment;
    }
}

class Chat extends Animal{

    String miaulement;

    public Chat(String surnom, String miaulement) {
        super(surnom);
        this.miaulement = miaulement;
    }
}

Conclusion

A travers ces 3 exemples vous avez appris que:

  • Tous les champs d'un programme, même en privés sont consultables et modifiables.

  • La protection d'un champ par un "private" ou "protected" ne constitue pas une sécurité suffisante quand elle est côté client.

  • L'utilisation de la réflexion peut être utile pour la modification des valeurs de champs (lorsque vous n'avez pas la main sur une librairie, ou lors de tests unitaires

  • La réflexion est utile de nombreux de processus d'industrialisation de code.

  • Mettre en place un code complexe permet à d'autres développeurs d'utiliser ce code sans complexité et peut leur faire gagner du temps grâce à la généricité des fonctions développées.

  • La réflexion à sa place dans la désérialisation JSON.

La réflexion est un outil que j'utilise au quotidien dans ma vie professionnelle ( que ce soit en JAVA ou en C#). Je l'utilise particulièrement lors des tests unitaires. Je ferai plus tard un article sur ce sujet.

Cependant il faut noter que la réflexion n'est pas forcément performante. Les temps de calculs peuvent être long. Elle doit donc être utilisé dans de petits processus, où lorsque aucune autre solution n'est envisageable.

Sur les tests unitaires cela ne pose aucun problème puisque ce n'est pas du code qui est fait pour aller en production.

La réflexion n'est pas forcément enseignée dans les cursus des élèves étudiants l'informatique. Il est nécessaire de se l'approprier en faisant quelques tests dans des projets personnels pour bien comprendre son utilité, et à quel moment elle peut être utilisée.

Bibliographie

https://fr.wikipedia.org/wiki/R%C3%A9flexion_(informatique)

https://www.jmdoudoux.fr/java/dej/chap-introspection.htm

https://www.geeksforgeeks.org/reflection-in-java/

Connaissances personnelles

A propos de SUPINFO | Contacts & adresses | Enseigner à SUPINFO | Presse | Conditions d'utilisation & Copyright | Respect de la vie privée | Investir
Logo de la société Cisco, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société IBM, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Sun-Oracle, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Apple, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Sybase, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Novell, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Intel, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Accenture, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société SAP, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Prometric, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Toeic, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo du IT Academy Program par Microsoft, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management

SUPINFO International University
Ecole d'Informatique - IT School
École Supérieure d'Informatique de Paris, leader en France
La Grande Ecole de l'informatique, du numérique et du management
Fondée en 1965, reconnue par l'État. Titre Bac+5 certifié au niveau I.
SUPINFO International University is globally operated by EDUCINVEST Belgium - Avenue Louise, 534 - 1050 Brussels