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

Langage Java Discussion :

Génération méthode toString grâce à la réflexivité et la récursivité


Sujet :

Langage Java

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre confirmé Avatar de maelstrom
    Homme Profil pro
    Développeur Java
    Inscrit en
    Mars 2014
    Messages
    108
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 31
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur Java
    Secteur : Finance

    Informations forums :
    Inscription : Mars 2014
    Messages : 108
    Par défaut Génération méthode toString grâce à la réflexivité et la récursivité
    Bonjour à tous,

    Je me suis récemment confronté à un petit problème en Java. Effectivement je suis en train de développer un programme où l'on me demande à chaque fois que je fais appel à une méthode d'afficher dans les logs toutes les variables du ou des objets en entrée de la méthode ainsi que toutes les variables de l'objet en sortie. Sachant que certain de ces objets ont parfois jusqu'à 50 variables faire des .get() à la chaîne n'est évidemment pas une solution d'autant plus que si l'objet évolue la log devient obsolète.

    Bon là normalement vous allez me dire "Mais rien de plus simple, tu n'as qu'à surenchérir la méthode toString dans tes objets !", oui je serais d'accord avec vous, mais voilà il y a un hic. Ces objets sont générés via un outil de modélisation et il est interdit de les retoucher à la main derrière :/

    J'ai donc eu une idée, créer une méthode grâce à la réflexivité et la récursivité qui créerait un String à la volée avec toutes les variables de l'objet en entrée de la méthode. J'ai commencé à travailler sur cette piste et voici la méthode telle que je la vois pour le moment :

    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
    public static String objectToString(Object obj) throws IllegalArgumentException, IllegalAccessException
    {
    	String toString = "[";
    	boolean fistField = true;
    	Field[] fields = obj.getClass().getDeclaredFields();
     
    	//on parcourt tous les champs de l'objet
    	for (Field field : fields)
        {
    		//on rend accessible le champ pour récupérer sa valeur
    		field.setAccessible(true);
     
    		//Si ce n'est pas le premier objet on rajoute une virgule
    		if(!fistField)
    		{
    			toString = toString + ", ";
    		}
     
    		//Si le type de la variable du champ n'est ni une primitive, ni un enum
    		//et que la longueur son nom est plus petit que 10 ou égal et supérieur à 10
    		//mais que son début n'est pas églale à "class java" afin d'éviter les classes de Type HashMap, ArrayList, ect...
    		if (!field.getType().isPrimitive() && !field.getType().isEnum() && (field.getType().toString().length() < 10 ||
    				field.getType().toString().length() >= 10 && !field.getType().toString().substring(0, 10).equals("class java")))
    		{
    			//alors c'est que c'est un Objet
    			//on peut donc faire appel à cette méthode de manière récursive
    			//on rapelle donc cette méthode afin de récupérer les valeurs des champs de l'objet
    			toString = toString + field.getName() + "=" +objectToString(field.get(obj));
    		}
    		else
    		{
    			toString = toString + field.getName() + "=" + field.get(obj);
    		}
     
    		//on rend de nouveau non accessible le champ 
    		field.setAccessible(false);
     
    		fistField = false;
    	}
    	toString = toString + "]";
     
    	return toString;
    }
    En l'état, cette méthode "fonctionne", on peut obtenir un String un résultat qui a cette tête là :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Objet1: [variableIntObj1=1, variableStringObj1=test, variableHashMapObj1={variableStringList1=123, variableStringList2=456}, Objet2=[variableDoubleObj2=123.0, variableFloatObj2=12.123], variableEnumObj1=TATA, variableBigIntegerObj1=11]
    Ça fonctionne donc avec les types primitifs, les objets (grâce à la récursivité), les listes, les map, les enums et les objets de primitives (Double, Integer, BigDecimal, etc...). Seulement comme vous pouvez le voir dans le code, cela fonctionne, mais je l'ai fait un peu de manière "Brutal", en tout cas ça ne me plait pas vraiment en l'état.

    Pour cause cette ligne de code dans mon if :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    !field.getType().toString().substring(0, 10).equals("class java")
    Je fais cela en fait pour tester que l'objet que je teste ne soit pas un objet du package java et donc que l'objet ne soit pas une liste, collection, ou objet de primitive afin de ne pas utiliser la récursivité dessus. Cette façon de façon de faire à des limites et elle n'est pas élégante, par exemple si j'ai une liste d'objet et bien je ne vais pas afficher leurs variables, mais l'adresse de l'objet.

    Donc y a-t-il une manière de "mieux" faire ? Ou alors est-ce que je me complique trop la vie et il y a une manière beaucoup plus simple de faire ce que je souhaite ?

    Merci d'avance pour vos réponses

  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
    Salut,

    Tu devrais regarder Apache Commons ReflectionToStringBuilder. La sélection de classe se fait par redéfinition de la méthode ToStringStyle.accept(Class<?>clazz) (tu peux tester le nom du package : !clazz.getPackage().getName().startsWith("java.lang") )
    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
    Expert éminent
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Billets dans le blog
    1
    Par défaut
    Salut,


    D'accord avec joel.drigo, tu as plein de librairie qui font cela...

    Sinon je ne trouve pas cela si problématique :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    !field.getType().toString().substring(0, 10).equals("class java")
    => Mais autant faire directement :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    !field.getType().getName().startsWith("java")

    Pour ton problème avec les Collections/Map, tu peux simplement adapter ton code pour les traiter spécifiquement...


    Sinon plusieurs remarques :
    • Tu t'embêtes avec les exceptions, tu devrais soit les englober dans une unchecked-exception (pour ne pas avoir à les traiter) soit les gérer en affichant une erreur au niveau de l'attribut.
    • Tu ne gères pas les valeurs null !
    • Tu affiches les attributs static, et je ne pense pas que ce soit volontaire...
      Utilises Modifier.isStatic(field.getModifiers()) pour les filtrer
    • De même certains objets peuvent avoir des attributs "synthetic" (généré par le compilateur), et je ne pense pas qu'il soit souhaitable de les afficher.
      Tu peux les filtrer avec field.isSynthetic()
    • Tu bases ton affichage sur le type déclaré de l'objet, et non pas sur le type réel, donc tu pourrais avoir des incohérences.
      Il vaut mieux baser cela sur le code de l'instance et non pas sur le field.
    • Et surtout attention avec l'opérateur + sur les String.
      Cela génère un paquet d'objet temporaire et cela peut être très gourmand en mémoire si tu dois représenter des objets un tant soit peu conséquent.
      Il serait préférable d'utiliser un StringBuilder pour construire ta chaine...



    a++

  4. #4
    Membre confirmé Avatar de maelstrom
    Homme Profil pro
    Développeur Java
    Inscrit en
    Mars 2014
    Messages
    108
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 31
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur Java
    Secteur : Finance

    Informations forums :
    Inscription : Mars 2014
    Messages : 108
    Par défaut
    Je vous remercie pour vos deux réponses intéressantes.

    Effectivement je ne connaissais pas cette bibliothèque, j'ai du coup quelques questions sur son fonctionnement. Donc j'ai un peu testé deux ou trois trucs, mais je n'arrive pas bien à la faire fonctionner aussi bien que je le voudrais.

    J'ai essayé ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    ReflectionToStringBuilder.toString(toto)
    Mais ça ne fait pas la récursivité et ça met comme nom pour les objets, le nom suivis de l’adresse, je trouve ça un peu dommage, ça nuit à la lisibilité.

    Pour la récursivité j'ai ajouté ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    ReflectionToStringBuilder.toString(toto, new RecursiveToStringStyle())
    Mais ça récursive sur tout, même les classes List, Collection, etc...

    Après j'ai essayé de rajouté la méthode accept() comme expliqué dans ton message pour exclure les classes java, malheureusement cette méthode est protected donc invisible. Il faut passer par la méthode appendDetail() qui elle fera appel à la méthode accept(), mais je n'ai pas réussi à la faire fonctionner comme je le souhaitais.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    RecursiveToStringStyle recursiveToStringStyle = new RecursiveToStringStyle();
    recursiveToStringStyle.appendDetail(???, ???, !Toto.class.getPackage().getName().startsWith("java.lang"));

  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
    Salut,

    1. Tu as différentes méthodes qui te permettent de configurer des options (avec ou pas le nom de la classe par exemple) :

      Code : Sélectionner tout - Visualiser dans une fenêtre à part
      1
      2
      3
      4
      5
      6
      7
      8
      9
      System.out.println(ReflectionToStringBuilder.toString(var, new RecursiveToStringStyle() {
       
      			private static final long serialVersionUID = 1L;
       
      			{
      				setUseClassName(false);
                              }
       
      });
    2. Tu peux également redéfinir des méthode du "style", pour adapter :
      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
      public class DemoReflectionToStringBuilder {
       
      	private int prop1=42;
      	private Machin prop2=new Machin();
      	private List<Truc> prop3=Arrays.asList(new Truc(1),new Truc(2),new Truc(3));
      	private List<Truc> prop4=Collections.emptyList();
      	private String prop5;
       
      	public static void main(String[] args) {
       
      		DemoReflectionToStringBuilder var = new DemoReflectionToStringBuilder();
       
      		System.out.println(ReflectionToStringBuilder.toString(var, new RecursiveToStringStyle() {
       
      			private static final long serialVersionUID = 1L;
       
      			{
      				setUseClassName(false);
      				setUseIdentityHashCode(false);
      				setContentStart("{");
      				setContentEnd("}");
      				setArrayStart("[");
      				setArrayEnd("]");
      				setArraySeparator(", ");
      				setFieldNameValueSeparator(":");
      				setFieldSeparator(", ");
      				setNullText("*");
      			} 
       
      			@Override
      			protected void appendDetail(StringBuffer buffer, String fieldName, Collection<?> coll) {
      				if ( coll.isEmpty() ) {
      					buffer.append("\u00d8");
      				}
      				else {
      					super.appendDetail(buffer, fieldName, coll);
      				}
      			}
       
      			@Override
      			public void appendDetail(StringBuffer buffer, String fieldName, Object value) {
      				if ( value instanceof String ) {
      					buffer.append('"');
      					super.appendDetail(buffer, fieldName, value);
      					buffer.append('"');
      				}
      				else {
      					super.appendDetail(buffer, fieldName, value);
      				}
      			} 
      		}));
       
       
      	}
       
      	public static class Machin {
      		private String s = "blahblah";
      	}
      	public static class Truc {
      		private int val;
      		public Truc(int val) {
      			this.val=val;
      		}
      	}
       
      }
    3. C'est justement en redéfinissant la méthode accept(Class) de RecursiveToStringStyle (et non en l'appelant) que tu vas pouvoir définir quelles classes parcourir récursivement ou non. Et le fait qu'elle soit protected ne t'empêche justement de la redéfinir, juste de l'appeler.
    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.

Discussions similaires

  1. PHP Génération méthodes
    Par defcon_suny dans le forum BOUML
    Réponses: 5
    Dernier message: 09/09/2008, 08h57
  2. Erreur sur l'appel de la méthode ToString
    Par Emcy dans le forum C#
    Réponses: 3
    Dernier message: 31/03/2008, 13h05
  3. Méthode toString d'un vector
    Par FranT dans le forum Langage
    Réponses: 6
    Dernier message: 31/10/2006, 17h53
  4. Problème d'affichage avec le méthode toString()
    Par Premium dans le forum Langage
    Réponses: 8
    Dernier message: 26/10/2006, 11h36
  5. Affichage sur plusieurs lignes d'une méthode toString
    Par Flophx dans le forum Interfaces Graphiques en Java
    Réponses: 9
    Dernier message: 24/05/2006, 16h30

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