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

  1. #1
    Membre régulier
    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
    Points : 85
    Points
    85
    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 : 54
    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
    Points : 29 131
    Points
    29 131
    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 régulier
    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
    Points : 85
    Points
    85
    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 régulier
    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
    Points : 85
    Points
    85
    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 : 54
    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
    Points : 29 131
    Points
    29 131
    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 régulier
    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
    Points : 85
    Points
    85
    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 !!!

  7. #7
    Membre régulier
    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
    Points : 85
    Points
    85
    Par défaut
    Bonjour Joël !

    J'ai fait des essais mais si j'ajoute au HashSet "classFiltered" la classe en cours d'introspection je n'ai pas les champs. Je voyais "classFiltered" comme un filtre de classe ou plus précisément, vu que l'intérêt du HashSet est de ne pas accepter les doublons, je pensais que lors de l'ajout de ma classe dans celui-ci, il faisait tout naturellement le trie en ne gardant que le nom de la classe est les champs différents des paramètres renseignés dans le static (je pense que static équivaut à constante ?).

    C'est juste une base de test pour voir ce qui est contenu dans le HashSet et je m'aperçois bien évidemment que le nom des filtres apparaît dans la liste donc faut-il que je fasse un 2ème Itérateur pour comparaît les valeurs du "set" et de "classFiltered" pour ne pas les afficher ?
    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
     
    	public static void display(String aClass) throws ClassNotFoundException, NoSuchFieldException, SecurityException {
     
    		Class c = Class.forName(aClass);
     
    		Set<Class<?>> classFiltered = new HashSet<Class<?>>(Introspection.set);
     
    		/** Test si la classe a déjà scruté */
    		if (!classFiltered.contains(c)){
     
    			/** Ajout de la classe dans le HashSet */
    			classFiltered.add(c);
     
    			Iterator<Class<?>> it1 = classFiltered.iterator();
    			while (it1.hasNext()) {
    				System.out.println(it1.next());
    			}
     
    			/** Nom de la classe */
    			System.out.println("@" + c.getName());
     
    			/** Liste le contenu de la classe */
    			ListField(c);
    		}
     
    		/** En attente de modification : APPEL RECURSIF DE LA METHODE */
    		//display(aClass);
    	}
    Alors faut-il que j'ajoute ceux-ci via les méthodes "getName()" et "getType().getName()" dans classFiltered ? Je pense mais je ne vais pas m'avancer qu'après les choses devraient en découler d'elles mêmes mais vu que la dernière fois j'ai dit ça et que tout est faux ..... .

    Merci d'avance et désolé de ne pas avoir était à la hauteur dès la 1ere fois.

  8. #8
    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 : 54
    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
    Points : 29 131
    Points
    29 131
    Billets dans le blog
    2
    Par défaut
    Bonjour,

    Set<Class<?>> classFiltered = new HashSet<Class<?>>(Introspection.set); créé un nouveau set, et l'initialise avec le contenu de Introspection.set.

    Ensuite quand tu fais classFiltered.add(c);, ça ajoute la classe c à classFiltered. Le set ici ne sert pas seulement à ne pas avoir 2 fois la même valeur (ce qui ne serait pas vraiment un problème pour obtenir le résultat voulu, mais juste qu'on stockerait inutilement des données plusieurs fois éventuellement), mais c'est aussi parce que un hashset est particulièrement efficace pour le test d'appartenance (fait par contains()), si tant est que equals() et hashcode() soit correctement implémentés ce qui est le cas pour la classe Class.

    Comme il faut afficher le nom de la classe dans tous les cas, et seulement omettre ses champs, et de fait la récursivité sur les classes de ses champs, on doit donc toujours afficher le nom de la classe d'un champ, et ensuite tester si on a déjà affiché son détail et le faire si non (et pas le faire si oui).

    C'est pour çà que j'ai écrit :
    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
    27
    afficherClasse( classeAAfficher ){
     
         afficherClasse( classeAAfficher, nouvelle trace vide); // ici, il faudrait plutôt faire afficherClasse( classeAAfficher, nouvelle trace initialisée avec le set static)
     
    }
     
    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
     
    }

    Si on prend un exemple simple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    public class Exemple {
       private Exemple var1;
       private Integer var2;
    }
    On va avoir le déroulement suivant :
    1. on initialise classFiltered avec le set static (des classes qu'on ne veut pas détailler)
    2. on appelle notre méthode d'affichage avec Exemple en paramètre, et classFiltered
      1. on affiche le nom de Exemple : @Exemple
      2. on teste si Exemple est dans classFiltered : non
      3. on ajoute Exemple à classFiltered pour que ce ne soit plus détaillé ensuite
      4. on parcourt les champs de Exemple : var1, var2
      5. on affiche le nom de var1, soit "var1" (je passe sur l'aspect private/public)
      6. on appelle récursivement la méthode d'affichage d'une classe avec Exemple (le type de var1) en paramètre (ce qui correspond à ce qu'on fait en 2
        1. on affiche donc @Exemple
        2. on teste si Exemple est dans classFiltered : oui => on s'arrête et on remonte
      7. on affiche le nom de var2
      8. on appelle récursivement la méthode d'affichage d'une classe avec Integer (le type de var2) en paramètre
        1. on affiche donc @Integer
        2. on teste si Integer est dans classFiltered : oui => on s'arrête et on remonte


    Il n'y a rien d'autre à ajouter dans classFiltered que la classe dont on vient d'afficher le nom : ce qui est important c'est de conserver la même instance de set dans les appels récursifs, pour que celui-ci cumule les différentes classes déjà détaillées.

    Si par contre le but de Introspection.set est de ne pas du tout afficher les champs dont les types sont dans ce set, il faudra donc 2 sets : un pour filtrer les classes dont on ne veut pas le détail (le tiens, que tu initialiseras vide), un autre pour filtrer les champs qu'on ne veut pas afficher (Introspection.set).

    On pourrait avoir une troisième variante : on ne veut pas détailler une classe qu'on a déjà détaillée dans les niveaux au dessus. Il faudrait récréer un set à chaque appel récursif (pour chaque champ) en l'initialisant avec le contenu du set de l'appel courant
    Par exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    public class Exemple {
       private Exemple2 var1;
       private Exemple2 var2;
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    public class Exemple2 {
       private Exemple var;
    }
    donnerait
    @Exemple
       -var1 : @Exemple2
           -var @Exemple
       -var2 : @Exemple2
           -var @Exemple
    
    Avec l'algo que je t'ai donné, ça donnerait :
    @Exemple
       -var1 : @Exemple2
           -var @Exemple
       -var2 : @Exemple2
    
    Par ailleurs, static ne veut pas dire "constante", mais plutôt en quelque sorte "partagé entre les instances" (unique pour toutes les instances). C'est la même instance de set qui est utilisée par toutes les instances de Introspection. Si Introspection.set était final, on ne pourrait pas mettre un autre set à la place (refaire par exemple Introspection.set = quelquechose). Mais ça rendrait pas vraiment constant le set, puisqu'on pourrait toujours ajouter ou enlever des éléments dans le set.
    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.

  9. #9
    Membre régulier
    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
    Points : 85
    Points
    85
    Par défaut
    Ok ! Merci pour toutes ses informations cruciales mais compliquées pour ma petite tête à digérer donc patience ... il me faut mettre ça en pratique. Je suis revenu sur ton exemple de début où tu m'expliques comment gérer ma récursivité et je ne comprend pas bien une chose (désolé ! au bout d'un moment je deviens pardonnez moi à ceux qui vont lire ce post "Chiant" et je le comprends mais après il y a ceux qui ont un dont et ensuite ceux qui sont comme moi), je vois une méthode nommée "afficherClasse( classeAAfficher )" avec en passage de paramètre "classeAAfficher " ensuite une autre méthode nommée "afficherClasse( classeAAfficher , traces)" avec cette fois-ci 2 paramètres.
    Pour les paramètres je les comprends en revanche il y a 2 fois le même nom de méthode et j'arrive pas bien à comprendre, c'est peut être une erreur de syntaxe ? Autant l'exo précédent pour lequel tu m'as grandement aidé avait été très claire et j'ai pu tout mettre en oeuvre grace à tes conseils et le finaliser, autant celui-ci je suis sur la comète Tchouri avec Philae ... seul dans l'univers !!!

    Je pense que je vais faire de la récursivité avec tous les Mercis que je te dis à chaque fois ;-)

  10. #10
    Membre régulier
    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
    Points : 85
    Points
    85
    Par défaut
    Je vais rajouter en complément de mon précédent post, le code que je viens de faire en me basant sur les différentes étapes que tu m'as décrit. J'ai des résultats mais il y a 2 choses qui ne vont pas :
    1 - Dans l'énoncé de mon exo le résultat ne correspond pas avec ce que j'ai mais je pense que c'est de ma faut car j'ai mal expliqué le résultat escompté donc je vais le remettre.
    2 - COmment faire pour que mon affichage lors de l'appel récursif soit décalé ?

    Résultat attendu :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    @Classe.atelier10.PileSimple
         - top : @ Classe.atelier10.Liste
                   + value : @java.lang.Object
                   + next : @ Classe.atelier10.Liste
    Code que je fais en essayant de respecter les étapes que tu m'as décrit
    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
     
    	/** LISTE LES CHAMPS DE LA CLASSE 
             * @throws ClassNotFoundException 
             * @throws SecurityException 
             * @throws NoSuchFieldException */
    	private static void ListField(Class<?> class1, String aClass) throws ClassNotFoundException, NoSuchFieldException, SecurityException{
     
    		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);
    			}			
    			display(returnString);
    		}
    	}
     
    	@SuppressWarnings({ "rawtypes" })
    	public static void display(String aClass) throws ClassNotFoundException, NoSuchFieldException, SecurityException {
     
    		Class c = Class.forName(aClass);
     
    		/** Nom de la classe */
    		System.out.println("@" + c.getName());
     
    		/** Test si la classe a déjà scruté */
    		if (!classFiltered.contains(c)){
     
    			/** Ajout de la classe dans le HashSet */
    			classFiltered.add(c);
     
    			/** Liste le contenu de la classe */
    			ListField(c,aClass);
    		}		
    	}
    Merci d'avance.

  11. #11
    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 : 54
    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
    Points : 29 131
    Points
    29 131
    Billets dans le blog
    2
    Par défaut
    Citation Envoyé par Jb_One73 Voir le message
    ...je ne comprend pas bien une chose (désolé ! au bout d'un moment je deviens pardonnez moi à ceux qui vont lire ce post "Chiant" et je le comprends mais après il y a ceux qui ont un dont et ensuite ceux qui sont comme moi), je vois une méthode nommée "afficherClasse( classeAAfficher )" avec en passage de paramètre "classeAAfficher " ensuite une autre méthode nommée "afficherClasse( classeAAfficher , traces)" avec cette fois-ci 2 paramètres...
    On est souvent obligé de procéder comme ça lorsqu'on a besoin d'avoir une mémorisation au fur et à mesure des appels, dans un set, ou une map. On a une méthode principale, public, qui est la méthode qui sera appelée pour faire le traitement, et puis on a une méthode privée, avec un (ou plusieurs) paramètre supplémentaire, le set, ou la map. Il s'agit d'un artifice technique permettant de réaliser le traitement : on ne l'expose donc pas à l'utilisateur de la méthode. On cache ainsi la partie cuisine technique interne. Sans parler du fait qu'on pourrait très bien programmer un autre moyen de résoudre le problème par la suite sans avoir à toucher la méthode utile.

    Par exemple, pour compter le nombre de valeur distinct dans un arbre :

    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
    public static int compterDistinct(Node node) { 
      return compterDistinct(node, 0, new HashSet<>()); // compte les occurences distinct
    }
     
    private static int compterDistinct(Node node, int compteur, Set<Object> valeurDejaComptees) {
      Object value = node.getValue();
      if ( !valeurDejaComptees.contains(value) ) { // si on n'a jamais compté cette valeur
        compteur++; // on la compte 
        valeurDejaComptees.add(value); // on la mémorise comme ayant déjà été comptée
      }
      // on compte récursivement dans les fils
      for (Node child : node.getChildren()) {
        compteur = compterDistinct(child, compteur, valeurDejaComptees);
      }
      return compteur;
    }
    Celui qui appelle la méthode compterDistinct(Node) se fout à la limite qu'on utilise un Set ou autre chose. En tout cas, il ne devrait pas avoir à en fournir un (ce qui serait le cas si on avait que la seconde méthode). Idem pour le compteur.

    Autre exemple, pour obtenir la valeur qui se trouve le plus souvent dans un arbre de Node, on pourrait é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
    20
    21
    22
    23
    public static Object compter(Node node) {
     
      Map<Object, Integer> compteurs = new HashMap<>();
      compter(node, compteurs); // compte les occurences de chaque valeur
     
      return compteurs.entrySet().stream()
        .max( (o1, o2)-> o1.getValue() - o2.getValue() )
        .get().getKey(); // cherche la valeur qui a le maximum de compte et la retourne
     
    }
     
    private static void compter(Node node, Map<Object, Integer> compteurs) {
      Object value = node.getValue();
      if (compteurs.containsKey(value)) { // si la valeur est déjà compté, on incrémente son compteur
        compteurs.put(value, compteurs.get(value) + 1);
      } else { // sinon on initialise son compteur à 1
        compteurs.put(value, 1);
      }
      // on compte récursivement dans les fils
      for (Node child : node.getChildren()) {
        compter(child, compteurs);
      }
    }
    En plus, ici, la méthode utilise la map pour faire un traitement final : raison de plus pour de ne pas l'avoir en paramètre visible.
    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.

  12. #12
    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 : 54
    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
    Points : 29 131
    Points
    29 131
    Billets dans le blog
    2
    Par défaut
    Quel est le résultat que tu obtiens ? Quelle sont exactement les différences avec :
    @Classe.atelier10.PileSimple
         - top : @ Classe.atelier10.Liste
                   + value : @java.lang.Object
                   + next : @ Classe.atelier10.Liste
    
    1. Je me doute qu'il y a déjà un problème de répétition du nom de la classe (vu que tu l'affiches 2 fois : une fois avant l'appel récursif, une fois dans la méthode appelée récursivement).
    2. Ensuite, il y a effectivement l'indendation.


    Pour le cas 1, on voit ici que tu affiches le nom de la classe, puis tu appelles la méthode display qui va réafficher le nom de la classe.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    /** 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);
       }			
       display(returnString);
    Soit tu t'arranges différemment, pour l'appel récursif, mais, à mon avis, le plus simple est d'écrire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
       if(java.lang.reflect.Modifier.isPublic(field[i].getModifiers())){
          System.out.print("   + " + methodString + " : ");
       }else{
          System.out.print("   - " + methodString + " : ");
       }			
       display(returnString);
    Voire
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
       System.out.print("   "); // indentation
       if(java.lang.reflect.Modifier.isPublic(field[i].getModifiers())){
          System.out.print("+");
       }else{
          System.out.print("-");
       }			
       System.out.print(" " + methodString + " : ");
       display(returnString);
    Et c'est le System.out.println("@" + c.getName()); qui se chargera d'afficher le nom de la classe, à chaque fois que nécessaire (en passant à la ligne systèmatiquement)

    Pour l'indentation, tu peux voir que j'ai marqué la seule ligne où on doit la gérer. Il suffit ici donc d'afficher un nombre de caractères dépendant de la profondeur d'appel. On peut passer ce nombre en paramètre de la méthode, comme on passe le set, et l'incrémenter à chaque appel récursif (de 2 par exemple, pour avoir un décalage de 2 à chaque niveau), et faire boucle pour afficher autant d'espace.
    Autre méthode, moins élégante, mais peut-être plus simple pour toi, utilise une chaîne de caractères, que tu augmentes à chaque appel récursif :

    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
    public static void display(Class<?> clazz) {
     
      display(clazz, new HashSet<Class<?>>(), ""); // on initialise l'indentation
     
    }
     
     
    private static void display(Class<?> clazz, HashSet<Class<?>> filtre, String indent) {
     
      System.out.println( "@"+clazz.getName() );
     
      if ( !filtre.contains( clazz ) ) {
     
        filtre.add(clazz);
        for(Field field : clazz.getDeclaredFields()) {
     
          String scope = Modifier.isPublic(field.getModifiers())?"+":"-";
     
          System.out.print( indent + scope + field.getName() + " : " );
     
          display( field.getType(), filtre, indent + "  " ); // on augmente l'indentation de 2 espaces à chaque appel récursif
     
        }
     
      }
     
    }
    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.

  13. #13
    Membre régulier
    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
    Points : 85
    Points
    85
    Par défaut
    Merci Joël !

    Tu m'as noté la solution complète dans le dernier code que tu as mis car j'ai fait un copié/collé, testé et ça fonctionne nickel !

    - Soit tu en as eu marre que je n'y arrive pas et donc tu m'as mis la solution.
    - Soit tu as pris pitié de moi et tu t'ais dit je vais l'aider pour qu'il réussisse son exercice.
    - Soit tu as pas fait attention et tu m'as noté la solution.

    Dans tous les cas, je te remercie énormément pour toutes ses explications qui n'ont pas été inutile et bien au contraire, je pense qu'ils feront la joie d'autres personnes qui seront certainement dans la même galère que moi.

    Je pense que l'on se reverra bientôt car des exos, il m'en reste un paquet mais j'espère ne pas être dépendant à chaque des autres.

    /** J'ajoute cette petite méthode récursive qui en dira long sur l'aide, les explications et ta patience que tu m'as alloué */
    Public static Merci(){
    system.out.println("MERCI");
    Merci();
    }

    @ très bientôt et si je ne t'ai pas d'ici là, passes de très bonnes fêtes de fin d'année.

  14. #14
    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 : 54
    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
    Points : 29 131
    Points
    29 131
    Billets dans le blog
    2
    Par défaut
    Non, c'est simplement que je tu étais tellement proche de la solution (à quelques détails), et qu'il me semblait que tu avais compris le principe du moins, qu'autant mettre une solution proche d'une solution complète (attention tout de même à Introspection.set, que je ne référence nulle part... donc attention au copier/coller ). Que cela te serve au moins à comprendre et à réussir la prochaine fois par toi-même.

    Bonnes fêtes également.
    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.

+ 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