Considérations relatives à Java toString ()

Même les développeurs Java débutants sont conscients de l'utilité de la méthode Object.toString () qui est disponible pour toutes les instances de classes Java et peut être remplacée pour fournir des détails utiles concernant toute instance particulière d'une classe Java. Malheureusement, même les développeurs Java chevronnés ne tirent parfois pas pleinement parti de cette puissante fonctionnalité Java pour diverses raisons. Dans cet article de blog, je regarde l'humble Java toString()et décris les étapes faciles à suivre pour améliorer l'utilitarisme de toString().

Implémenter explicitement (remplacer) toString ()

Peut-être que la considération la plus importante liée à l'obtention d'une valeur maximale toString()est d'en fournir des implémentations. Bien que la racine de toutes les hiérarchies de classes Java, Object, fournisse une implémentation toString () qui est disponible pour toutes les classes Java, le comportement par défaut de cette méthode n'est presque jamais utile. Le Javadoc pour Object.toString () explique ce qui est fourni par défaut pour toString () lorsqu'une version personnalisée n'est pas fournie pour une classe:

La méthode toString pour la classe Object renvoie une chaîne composée du nom de la classe dont l'objet est une instance, du caractère arobase `` @ '' et de la représentation hexadécimale non signée du code de hachage de l'objet. En d'autres termes, cette méthode renvoie une chaîne égale à la valeur de: getClass().getName() + '@' + Integer.toHexString(hashCode())

Il est difficile de trouver une situation dans laquelle le nom de la classe et la représentation hexadécimale du code de hachage de l'objet séparés par le signe @ sont utiles. Dans presque tous les cas, il est nettement plus utile de fournir un

toString()

implémentation dans une classe pour remplacer cette version par défaut.

Le Javadoc pour Object.toString () nous dit également ce qu'une toString()implémentation devrait généralement impliquer et fait également la même recommandation que je fais ici: override toString():

En général, la  toString méthode renvoie une chaîne qui "représente textuellement" cet objet. Le résultat doit être une représentation concise mais informative, facile à lire pour une personne. Il est recommandé que toutes les sous-classes remplacent cette méthode.

Chaque fois que j'écris une nouvelle classe, il y a plusieurs méthodes que j'envisage d'ajouter dans le cadre de l'acte de création d'une nouvelle classe. Ceux-ci inclus

hashCode ()

et

equals (objet)

le cas échéant. Cependant, d'après mon expérience et à mon avis, la mise en œuvre explicite

toString()

est toujours appropriée.

Si la "recommandation" Javadoc selon laquelle "toutes les sous-classes remplacent cette méthode" n'est pas suffisante (alors je ne présume pas que ma recommandation l'est non plus) pour justifier à un développeur Java l'importance et la valeur d'une toString()méthode explicite , alors je recommande de revoir Josh L'élément Java efficace de Bloch "Always Override toString" pour plus d'informations sur l'importance de l'implémentation toString(). Je pense que tous les développeurs Java devraient posséder une copie de Effective Java , mais heureusement, le chapitre avec cet élément toString()est disponible pour ceux qui n'en possèdent pas: Méthodes communes à tous les objets.

Maintenir / mettre à jour toString ()

Il est frustrant d'appeler explicitement ou implicitement un objet toString()dans une instruction de journal ou un autre outil de diagnostic et de renvoyer le nom de classe par défaut et le code de hachage hexadécimal de l'objet plutôt que quelque chose de plus utile et lisible. Il est presque aussi frustrant d'avoir une toString()implémentation incomplète qui n'inclut pas des éléments significatifs des caractéristiques et de l'état actuels de l'objet. J'essaie d'être assez disciplinés et Générez et suivre l'habitude de revoir toujours la toString()mise en œuvre ainsi que l' examen de la equals(Object)et hashCode()mise en oeuvre de tout travail de classe I sur ce qui est nouveau pour moi ou chaque fois que je suis d' ajouter ou de modifier les attributs d'une classe.

Juste les faits (mais tous / la plupart d'entre eux!)

Dans le chapitre de Java efficacementionné précédemment, Bloch écrit: "Lorsque cela est pratique, la méthode toString devrait renvoyer toutes les informations intéressantes contenues dans l'objet." Il peut être pénible et fastidieux d'ajouter tous les attributs d'une classe lourde d'attributs à son implémentation toString, mais la valeur pour ceux qui essaient de déboguer et de diagnostiquer les problèmes liés à cette classe en vaudra la peine. Je m'efforce généralement d'avoir tous les attributs non nuls significatifs de mon instance dans la représentation String générée (et j'inclus parfois le fait que certains attributs sont nuls). J'ajoute également généralement un texte d'identification minimal pour les attributs. C'est à bien des égards plus un art qu'une science, mais j'essaye d'inclure suffisamment de texte pour différencier les attributs sans matraquer les futurs développeurs avec trop de détails. Le plus important pour moi est d'obtenir les attributs 'valeurs et un certain type de clé d'identification en place.

Connaissez votre public

L'une des erreurs les plus courantes que j'ai vues des développeurs Java non débutants toString()est d'oublier à quoi et à qui toString()est généralement destiné. En général, il toString()s'agit d'un outil de diagnostic et de débogage qui facilite la journalisation des détails d'une instance particulière à un moment donné pour un débogage et des diagnostics ultérieurs. C'est généralement une erreur que les interfaces utilisateur affichent des représentations String générées par toString()ou de prendre des décisions logiques basées sur une toString()représentation (en fait, prendre des décisions logiques sur n'importe quelle String est fragile!). J'ai vu des développeurs bien intentionnés avoir toString()un format de retour XML à utiliser dans un autre aspect du code compatible XML. Une autre erreur importante est de forcer les clients à analyser la chaîne renvoyée partoString()afin d'accéder par programme aux données membres. Il est probablement préférable de fournir une méthode publique getter / accessor plutôt que de se fier à la toString()constante. Ce sont toutes des erreurs parce que ces approches oublient l'intention d'une toString()mise en œuvre. Ceci est particulièrement insidieux si le développeur supprime des caractéristiques importantes de la toString()méthode (voir le dernier élément) pour la rendre meilleure sur une interface utilisateur.

J'aime que les toString()implémentations aient tous les détails pertinents et fournissent un formatage minimal pour rendre ces détails plus acceptables. Ce formatage peut inclure des caractères de nouvelle ligne judicieusement sélectionnés [ System.getProperty("line.seperator");] et des tabulations, des deux points, des points-virgules, etc. Je n'investis pas autant de temps que je le ferais dans un résultat présenté à un utilisateur final du logiciel, mais j'essaye pour rendre le formatage suffisamment agréable pour être plus lisible. J'essaie d'implémenter des toString()méthodes qui ne sont pas trop compliquées ou coûteuses à maintenir, mais qui fournissent un formatage très simple. J'essaye de traiter les futurs mainteneurs de mon code comme j'aimerais être traité par les développeurs dont je maintiendrai un jour le code.

Dans son article sur la toString()mise en œuvre, Bloch déclare qu'un développeur doit choisir s'il souhaite ou non toString()renvoyer un format spécifique. Si un format spécifique est prévu, cela doit être documenté dans les commentaires Javadoc et Bloch recommande en outre de fournir un initialiseur statique capable de renvoyer l'objet à ses caractéristiques d'instance en fonction d'une chaîne générée par le toString(). Je suis d'accord avec tout cela, mais je pense que c'est plus de problèmes que la plupart des développeurs ne sont prêts à le faire. Bloch souligne également que toute modification de ce format dans les versions futures causera de la douleur et de l'angoisse aux personnes qui en dépendent (c'est pourquoi je ne pense pas que ce soit une bonne idée que la logique dépende d'une toString()sortie). Avec une discipline importante pour rédiger et maintenir une documentation appropriée, ayant un format prédéfini pour untoString()pourrait être plausible. Cependant, il me semble difficile et préférable de simplement créer une nouvelle méthode distincte pour de telles utilisations et de ne pas toString()gêner.

Aucun effet secondaire toléré

Aussi importante que soit l' toString()implémentation, il est généralement inacceptable (et certainement considéré comme une mauvaise forme) d'avoir l'appel explicite ou implicite de toString()logique d'impact ou de conduire à des exceptions ou à des problèmes de logique. L'auteur d'une toString()méthode doit veiller à s'assurer que les références sont vérifiées pour null avant d'y accéder pour éviter une exception NullPointerException. Bon nombre des tactiques que j'ai décrites dans l'article Gestion efficace de Java NullPointerException peuvent être utilisées dans la toString()mise en œuvre. Par exemple, String.valueOf (Object) fournit un mécanisme simple pour une sécurité nulle sur les attributs d'origine douteuse.

Il est tout aussi important pour le toString()développeur de vérifier les tailles de tableau et d'autres tailles de collection avant d'essayer d'accéder à des éléments en dehors de cette collection. En particulier, il est trop facile d'exécuter une exception StringIndexOutOfBoundsException lorsque vous essayez de manipuler des valeurs String avec String.substring.

Étant donné que l' toString()implémentation d' un objet peut facilement être invoquée sans que le développeur s'en rende compte consciencieusement, ce conseil pour s'assurer qu'il ne lance pas d'exceptions ou n'effectue pas de logique (en particulier la logique de changement d'état) est particulièrement important. La dernière chose que tout le monde souhaite est que l'acte de journalisation de l'état actuel d'une instance entraîne une exception ou un changement d'état et de comportement. Une toString()implémentation doit effectivement être une opération en lecture seule dans laquelle l'état de l'objet est lu pour générer une chaîne à retourner. Si des attributs sont modifiés au cours du processus, de mauvaises choses risquent de se produire à des moments imprévisibles.

Ma position est qu'une toString()implémentation ne devrait inclure que l'état dans la chaîne générée qui est accessible dans le même espace de processus au moment de sa génération. Pour moi, il n'est pas défendable qu'une toString()implémentation accède à des services distants pour créer la chaîne d'une instance. Peut-être un peu moins évident est qu'une instance ne doit pas remplir les attributs de données parce qu'elle a toString()été appelée. L' toString()implémentation ne doit rendre compte que de la façon dont les choses se trouvent dans l'instance actuelle et non de la façon dont elles pourraient être ou seront dans le futur si certains scénarios différents se produisent ou si les choses sont chargées. Pour être efficace dans le débogage et les diagnostics, il toString()faut montrer comment les conditions sont et non comment elles pourraient l'être.

Un formatage simple est sincèrement apprécié

Comme décrit ci-dessus, une utilisation judicieuse des séparateurs de ligne et des tabulations peut être utile pour rendre les instances longues et complexes plus acceptables lorsqu'elles sont générées au format String. Il existe d'autres "astuces" qui peuvent rendre les choses plus agréables. Non seulement il String.valueOf(Object)fournit une certaine nullprotection, mais il se présente également nullcomme la chaîne "null" (qui est souvent la représentation préférée de null dans une chaîne générée par toString (). Arrays.toString (Object) est utile pour représenter facilement des tableaux sous forme de chaînes ( voir mon article Stringifying Java Arrays pour plus de détails).

Inclure le nom de la classe dans la représentation toString

Comme décrit ci-dessus, l'implémentation par défaut de toString()fournit le nom de classe dans le cadre de la représentation de l'instance. Lorsque nous annulons explicitement cela, nous perdons potentiellement ce nom de classe. Ce n'est généralement pas un gros problème si vous consignez la chaîne d'une instance, car le cadre de journalisation inclura le nom de la classe. Cependant, je préfère être prudent et avoir toujours le nom de la classe disponible. Je ne me soucie pas de conserver la représentation du code de hachage hexadécimal par défaut toString(), mais le nom de classe peut être utile. Throwable.toString () en est un bon exemple. Je préfère utiliser cette méthode plutôt que getMessage ou getLocalizedMessage car la première ( toString()) inclut le Throwablenom de la classe de s alors que les deux dernières méthodes ne le font pas.

Alternatives à toString ()

Nous n'avons actuellement pas cela (du moins pas une approche standard), mais il a été question d'une classe Objects en Java qui contribuerait longtemps à préparer en toute sécurité et de manière utile des représentations String de divers objets, structures de données et collections. Je n'ai entendu parler d'aucun progrès récent dans JDK7 sur cette classe. Une classe standard dans le JDK qui fournissait une représentation String des objets même lorsque les définitions de classe des objets ne fournissaient pas d'explicite toString()serait utile.

Apache Commons ToStringBuilder peut être la solution la plus populaire pour créer des implémentations sécurisées de toString () avec quelques contrôles de formatage de base. J'ai déjà blogué sur ToStringBuilder et il existe de nombreuses autres ressources en ligne concernant l'utilisation de ToStringBuilder.

Le conseil technique de Glen McCluskey sur la technologie Java «Écrire des méthodes toString» fournit des détails supplémentaires sur la façon d'écrire une bonne méthode toString (). Dans l'un des commentaires du lecteur, Giovanni Pelosi indique une préférence pour la délégation de la production d'une représentation sous forme de chaîne d'une instance dans les hiérarchies d'héritage de la toString()à une classe de délégué créée à cet effet.

Conclusion

Je pense que la plupart des développeurs Java reconnaissent la valeur de bonnes toString()implémentations. Malheureusement, ces implémentations ne sont pas toujours aussi bonnes ou utiles qu'elles pourraient l'être. Dans cet article, j'ai tenté de présenter quelques considérations pour améliorer les toString()implémentations. Bien qu'une toString()méthode n'ait pas (ou du moins ne devrait pas) avoir d'impact sur la logique comme une méthode equals(Object)ou hashCode()peut le faire, elle peut améliorer l'efficacité du débogage et du diagnostic. Moins de temps passé à déterminer l'état de l'objet signifie plus de temps à résoudre le problème, à passer à des défis plus intéressants et à satisfaire davantage les besoins des clients.

Cette histoire, "Java toString () Considerations" a été publiée à l'origine par JavaWorld.