IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Java Discussion :

Récursivité avec introspection sur une classe


Sujet :

Java

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éclairé
    Profil pro
    Inscrit en
    Août 2008
    Messages
    256
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2008
    Messages : 256
    Par défaut Récursivité avec introspection sur une classe
    Bonjour à tous,

    J'ai un exercice dans lequel je dois faire appel à de la récursivité, c'est à dire que je dois réinvoqué l'appel de ma méthode à l'intérieur de celle-ci.
    Dans un 1er temps à l'aide de l'introspection "reflect", je récupère la structure de la classe appelée (nom de la classe et ses champs), pour ça je pense que c'est bon, jai dû le faire correctement.
    Dans un 2ème temps (c'est là où les choses se corsent), je dois afficher via system.out.println() de nouveau les champs de la classe précédente dans celle que je suis entrain de lire et ça je ne sais pas comment faire.

    je vous met un morceau de l'énoncé de mon exo afin que ça soit plus claire ainsi que le code que j'ai déjà fait.

    En vous remerciant d'avance à tous.

    Le but de cet exercice est d'ecrire une methode qui affiche recursivement la structure d'une classe. La
    methode display prend une classe en parametre (une String) et affiche recursivement la structure
    des champs publics et prives de cette classe. Par exemple, l'evaluation de
    display("NomClasse");
    pourrait afficher quelquechose du genre :
    @NomClasse
    - Chp1: @UneAutreClasse1
    + value : @java.lang.Object
    + next : @UneAutreClasse1
    La chaine @NomClasse est le nom qualifie de la classe. Suivent les champs de
    la classe (ici le seul champ Chp1) precedes du caractere '-' pour les champs prives et '+' pour les
    champs publics. Chaque champ est suivi de l'affichage (recursif) de sa classe. En cas de classe recursive,
    comme la classe UneAutreClasse1, la classe n'est affichee que la premiere fois ou elle apparait. Ainsi, le contenu
    de la classe UneAutreClasse1 est affichee une fois (la classe de l'attribut Chp1 de la classe NomClasse) et seul le
    nom (qualifie) de la classe UneAutreClasse1 est affichee pour le type de l'attribut next de cette meme classe.
    Plus generalement, (le contenu d'une classe n'est affiche(e) qu'une seule fois : si on execute les deux
    instructions suivantes :
    display("NomClasse");
    display("NomClasse2");
    on obtient
    @NomClasse
    + value : @java.lang.Object
    + next : @NomClasse
    @NomClasse
    - top : @NomClasse2
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
     
    	public static void display(String aClass) throws ClassNotFoundException, NoSuchFieldException, SecurityException {
     
    		Class c = Class.forName(aClass);
     
    		/** Nom de la classe */
    		System.out.println("@" + c.getName());
     
    		Field[] theMethods = c.getDeclaredFields();
     
    		for (int i = 0; i < theMethods.length; i++) {
     
    			String methodString = theMethods[i].getName();
    			String returnString = theMethods[i].getType().getName();
     
    			/** Les champs public ou privés */
    			if(java.lang.reflect.Modifier.isPublic(theMethods[i].getModifiers())){
    				System.out.println("   + " + methodString + " : @" + returnString);
    			}else{
    				System.out.println("   - " + methodString + " : @" + returnString);
    			}
    		}
    	}

  2. #2
    Modérateur
    Avatar de joel.drigo
    Homme Profil pro
    Ingénieur R&D - Développeur Java
    Inscrit en
    Septembre 2009
    Messages
    12 430
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 55
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Ingénieur R&D - Développeur Java
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2009
    Messages : 12 430
    Billets dans le blog
    2
    Par défaut
    (Re)Salut,

    hum, déjà, il n'y aurait pas une erreur là :

    @NomClasse
    + value : @java.lang.Object
    + next : @NomClasse
    @NomClasse
    - top : @NomClasse2
    Ce serait pas plutôt comme suit ?

    @NomClasse
    + value : @java.lang.Object
    + next : @NomClasse
    @NomClasse2
    - top : @NomClasse2
    Ensuite, le principe demandé est que de répéter récursivement les informations de classes pour chaque champ d'une classe, sauf si elles ont déjà été affichées.
    En gros, pour java.lang.Boolean, on aurait :

    @java.lang.Boolean
    +TRUE : @java.lang.Boolean
    +FALSE : @java.lang.Boolean
    +TYPE : @java.lang.Class
    ...
    Pour la récursivité, on doit avoir 2 choses au moins : une méthode qui s'appelle elle-même (directement, ou indirectement éventuellement) et deux, une condition d'arrêt de la récursivité, pour éviter une récursion à l'infini. Il y a 2 conditions d'arrêt : une évidente, quand la classe n'a pas d'attribut (ou que tous les attributs sont de type primitifs, mais ça revient au même : ils n'ont pas d'attribut), et la seconde, est donnée dans l'enoncée : on ne doit pas répéter la récursivité sur une classe qu'on a déjà affichée.

    pour traiter cette condition d'arrêt, si on traitait que le cas à un seul niveau, comme celui énoncé dans le cas que je cite ci-dessus, on pourrait tester l'égalité entre le type du champ et le nom de la classe.

    Mais on n'a pas que 2 niveaux. Donc, il faut garder la trace de toutes les classes qu'on a déjà affichées, pour savoir à un moment donné qu'on les a pas déjà affichées. Cette trace va se concrétiser par un objet qu'on va passer en argument de la méthode récursive, en la séparant en 2 parties : une partie à appeler, et une partie implémentation :

    Code pseudocode : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    afficherClasse( classeAAfficher ){
     
         afficherClasse( classeAAfficher, nouvelle trace vide);
     
    }
     
    afficherClasse( classeAAfficher , traces) {
     
      afficher nom de classeAAfficher
     
      si ( traces ne contient pas classeAAfficher ) {
     
         ajouter à "traces" le nom de classeAAfficher // on vient de l'afficher, et elle ne l'avait jamais été, on la met dans les classes à ne plus afficher
     
         pour tous les attributs de classeAAfficher {
     
            afficher le nom de l'attribut
     
            afficher( classe de l'attribut, traces )
     
     
         } // fin pour
     
       } // fin si
     
    }

    Pour mémoriser la liste des classes déjà affichées, la "trace", comme j'ai dit, tu peux utiliser par exemple un HashSet<String>(), dans lequel on mettra le nom de classes visitées.

    PS: c'est bizarre d'appeler une variable theMethod pour y mettre un Field. Appelles tes variables field et fieldType, ce sera plus logique.
    L'expression "ça marche pas" ne veut rien dire. Indiquez l'erreur, et/ou les comportements attendus et obtenus, et donnez un Exemple Complet Minimal qui permet de reproduire le problème.
    La plupart des réponses à vos questions sont déjà dans les FAQs ou les Tutoriels, ou peut-être dans une autre discussion : utilisez la recherche interne.
    Des questions sur Java : consultez le Forum Java. Des questions sur l'EDI Eclipse ou la plateforme Eclipse RCP : consultez le Forum Eclipse.
    Une question correctement posée et rédigée et vous aurez plus de chances de réponses adaptées et rapides.
    N'oubliez pas de mettre vos extraits de code entre balises CODE (Voir Mode d'emploi de l'éditeur de messages).
    Nouveau sur le forum ? Consultez Les Règles du Club.

  3. #3
    Membre éclairé
    Profil pro
    Inscrit en
    Août 2008
    Messages
    256
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2008
    Messages : 256
    Par défaut
    Merci pour tes réponses que je trouve très claire .... Tu es enseignant ?? ;-)

    Vu l'heure qu'il est, je ne pourrais te répondre qu'en fin de soirée (vers 17h30 - 18h), donc j'ai tout bien noté, je fais les essais à ce moment là et je posterai ce que j'ai fait en espérant bien évedemment apporter une réponse juste la 1ere fois.
    En tout cas, merci pour ton aide, tes réponses et tes explications.

  4. #4
    Membre éclairé
    Profil pro
    Inscrit en
    Août 2008
    Messages
    256
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2008
    Messages : 256
    Par défaut
    De retour avec un petit 1/4 H de retard, alors voilà mon Rembrandt mais qui ne marche pas ! Je pense ne pas être très loin de la solution mais il me manque encore un peu d'entraînement et de conseils.
    Je vais apporter une petite précision afin que je sois le plus claire possible. Alors pour commencer joel.drigo tu es trop fort ! La collection HashSet que tu m'as demandé d'utiliser et bien elle était déjà déclarer dans ma classe ce qui veut dire que l'enseignant veut que l'on passe par ce chemin, j'ai donc récupéré une bonne du code qu'il a commencé et que j'ai poursuivi (pas fini puisque ça ne marche pas :-( ).

    Alors, j'ai bien des choses mais à un certain un peu trop et je tourne en boucle.

    En voici l'extrait :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
     
    	// un Set pour stocker les classes déjà vues
    	private static HashSet<Class<?>> set = new HashSet<Class<?>>();
     
    	static {
    		try {
    			// on ajoute dans set les classes dont ne veut pas voir le contenu
    			set.add(Class.forName("java.lang.Integer"));
    			set.add(Class.forName("java.lang.String"));
    			set.add(Class.forName("java.lang.Class"));
    			// on pourrait en ajouter beaucoup plus 
    		}
    		catch ( ClassNotFoundException cnfe) {}
    	}
     
    	/** TEST SI LA CLASSE EST EXISTANTE */
    	private static boolean TestClasse(Class classe){
    		/** On test si la classe a déjà été traité */
    		return set.contains(classe);
    	}
     
    	/** AJOUT DE LA CLASSE DANS LA COLLECTION HashSet*/
    	private static void AjouteClasse(Class classe){
    		set.add(classe);
    	}
     
    	/** LISTE LES CHAMPS DE LA CLASSE 
             * @throws ClassNotFoundException */
    	private static void ListField(Class<?> class1) throws ClassNotFoundException{
     
    		Field[] field = class1.getDeclaredFields();
     
    		for (int i = 0; i < field.length; i++) {
     
    			String methodString = field[i].getName();
    			String returnString = field[i].getType().getName();
     
    			/** Les champs public ou privés */
    			if(java.lang.reflect.Modifier.isPublic(field[i].getModifiers())){
    				System.out.println("   + " + methodString + " : @" + returnString);
    			}else{
    				System.out.println("   - " + methodString + " : @" + returnString);
    			}
    		}
    	}
     
    	private static void ListFieldRecursif(Class<?> class1) throws ClassNotFoundException{
     
    		Field[] field = class1.getDeclaredFields();
     
    		for (int i = 0; i < field.length; i++) {
     
    			String methodString = field[i].getName();
    			String returnString = field[i].getType().getName();
     
    			/** Les champs public ou privés */
    			if(java.lang.reflect.Modifier.isPublic(field[i].getModifiers())){
    				System.out.println("      + " + methodString + " : @" + returnString);
    			}else{
    				System.out.println("      - " + methodString + " : @" + returnString);
    			}
    		}
    	}
     
    	public static void display(String aClass) throws ClassNotFoundException, NoSuchFieldException, SecurityException {
     
    		Class c = Class.forName(aClass);
     
    		/** Test si la classe a déjà scruté */
    		if (!TestClasse(c)){
     
    			/** Ajout de la classe dans le HashSet */
    			AjouteClasse(c);
     
    			/** Nom de la classe */
    			System.out.println("@" + c.getName());
     
    			/** Liste le contenu de la classe */
    			ListField(c);
    		}else{
    			Iterator<Class<?>> it = set.iterator();
    			while (it.hasNext()) {
    				ListFieldRecursif(it.next());
    			}
    		}
     
    		/** APPEL RECURSIF DE LA METHODE */
    		display(aClass);
    	}

  5. #5
    Modérateur
    Avatar de joel.drigo
    Homme Profil pro
    Ingénieur R&D - Développeur Java
    Inscrit en
    Septembre 2009
    Messages
    12 430
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 55
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Ingénieur R&D - Développeur Java
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2009
    Messages : 12 430
    Billets dans le blog
    2
    Par défaut
    1) Attention, le HashSet fournit par le prof est un HashSet static, initialisé en static. Il sert manifestement à préfiltrer des classes dont on ne veut pas avoir les champs. Si dans ton code tu modifies le contenu de ce set, tu ne pourras pas appeler plusieurs fois la méthode sur plusieurs classes, car lors d'un appel, le set filtrera toutes les classes rencontrées lors des appels précédent. Il faudrait à chaque appel, initialiser un HashSet local à la méthode et l'initialiser avec le contenu du set fournit par le prof.

    Il aurait pu, pour éviter qu'on puisse le faire écrire :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     
    	private static final Set<Class<?>> FILTERED_CLASSES;
     
    	static {
    		try {
    			// on ajoute dans set les classes dont ne veut pas voir le contenu
     
                            Set<Class<?>> set = new HashSet<?>();
     
    			set.add(Class.forName("java.lang.Integer"));
    			set.add(Class.forName("java.lang.String"));
    			set.add(Class.forName("java.lang.Class"));
    			// on pourrait en ajouter beaucoup plus 
     
                            FILTERED_CLASSES = Collections.unmodifiableSet( set );
     
    		}
    		catch ( ClassNotFoundException cnfe) {}
    	}
    Pour créer un HashSet local à ta méthode, on pourrait avoir :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    public void display(...) {
     
        Set<Class<?>> classFiltered = new HashSet<Class<?>>(LaClasse.set); // j'ai pas le nom de la classe dans ton extrait de code, donc j'ai mis LaClasse
     
        ...
     
    }
    2) Ensuite, observe les méthodes ListField(Class<?> class1) et ListFieldRecursif(Class<?> class1) : à part le fait que leur nom ne respecte pas la convention, qu'elles sont leurs différences ?

    Le but du set est de ne pas afficher les attributs d'une classe qui serait dans le set (de n'afficher que son nom d'après l'exemple), afin d'éviter de partir dans une récursion infini, du genre :

    @classtoto
    + mavariable @classtoto
       + mavariable @classtoto
          + mavariable @classtoto
             + mavariable @classtoto
                ...
                   à l'infini (enfin jusqu'à obtenir une StackTraceException, parce la mémoire, à fortiori la pile dans notre cas, n'est jamais infinie sur un ordinateur, pour l'instant en tout cas.
    
    3) Ensuite, regarde ta méthode display() et la position de l'appel récursif : je t'ai parlé de condition d'arrêt de récursivité. Le but est d'empêcher la méthode de s'appeler elle-même indéfiniment. Il est donc nécessaire que la méthode soit appelée forcément dans une condition, sinon elle sera forcément systématiquement appelée. Essaye, pour voir, ce petit programme :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class Infinite {
     
    	public void demo() {
    		demo(); // appel récursif
    	}
     
    	public static void main(String[] args) {
     
    		Infinite infinite = new Infinite(); // on créé une instance de la classe
     
    		infinite.demo(); // on appelle la méthode demo de la classe
     
    	}
     
    }
    En plus, cet appel récursif de display() prend en paramètre celui de la méthode elle-même : la récursion ne risque pas de s'arrêter (sauf StackTraceException).

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public void method(String param) {
     
        if( param.length>0 ) {
            String nouveauParam = param.subtring(1);
        }
     
        method(param);
     
    }
    ferait une récursion infinie. Si lorsque j'appelle method("toto), et que cet appel fait un appel de method("toto"), celui-ci fera donc aussi un appel de method("toto"), etc...jusqu'à ce que la mémoire de la jvm dédiée la pile soit remplie.

    Alors que :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public void method(String param) {
     
        if( param.length()>0 ) {
            System.out.println(param);
            String nouveauParam = param.substring(1); // je prend la chaine sauf le premier caractère (en quelque sorte, je supprime le premier caractère)
            method( nouveauParam ); // appel récursif
        }
     
    }
    oui, parce qu'on le paramètre tend vers la condition d'arrêt (sa taille est diminuée de 1 à chaque appel récursif, et quand sa taille vaut 0, on s'arrête).
    Si on appelle cette méthode avec "ABDC" en paramètre, on obtiendra :
    ABCD
    BCD
    CD
    D
    Je t'ai pourtant donné l'algorihme général : tu y verras où est placé l'appel récursif, et comment et où le set est utilisé.


    4) Enfin, pour terminer : si la classe est trouvée dans le set, on ne doit afficher que son nom, pas ses champs. Dans le cas de ton code, le else va être fait chaque fois que la classe sera dans le set, ce qu'on ne veut justement pas. En plus dans le bloc, tu parcours l'ensemble des classes dans le set pour en afficher les attributs. Donc pour une classe qui a un champ unique de classe Toto (qui n'est donc pas dans le set, au début en tout cas), tu afficheras les champs des classes Integer, String et Class, ce qui ne correspond pas à ce qui est demandé.


    PS. Non, je ne suis pas enseignant.
    L'expression "ça marche pas" ne veut rien dire. Indiquez l'erreur, et/ou les comportements attendus et obtenus, et donnez un Exemple Complet Minimal qui permet de reproduire le problème.
    La plupart des réponses à vos questions sont déjà dans les FAQs ou les Tutoriels, ou peut-être dans une autre discussion : utilisez la recherche interne.
    Des questions sur Java : consultez le Forum Java. Des questions sur l'EDI Eclipse ou la plateforme Eclipse RCP : consultez le Forum Eclipse.
    Une question correctement posée et rédigée et vous aurez plus de chances de réponses adaptées et rapides.
    N'oubliez pas de mettre vos extraits de code entre balises CODE (Voir Mode d'emploi de l'éditeur de messages).
    Nouveau sur le forum ? Consultez Les Règles du Club.

  6. #6
    Membre éclairé
    Profil pro
    Inscrit en
    Août 2008
    Messages
    256
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2008
    Messages : 256
    Par défaut
    Ok ! Bon ! Je vais poser tout ça à plat et je posterai mon évolution par la suite.
    Merci pour toutes ses explications !
    Au boulot !!!

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Appel d'une méthode sur une classe avec héritage
    Par SasakiKojiro dans le forum Général Java
    Réponses: 13
    Dernier message: 17/04/2015, 18h56
  2. Réponses: 2
    Dernier message: 28/02/2011, 09h45
  3. Introspection sur une classe abstraite
    Par sanchou dans le forum Général Java
    Réponses: 2
    Dernier message: 22/04/2010, 14h16
  4. Réponses: 1
    Dernier message: 04/11/2009, 15h33
  5. Probleme avec la recherche directe de methodes sur une Class (API java.lang.reflect)
    Par CyberChouan dans le forum API standards et tierces
    Réponses: 14
    Dernier message: 25/01/2007, 17h12

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo