Astuce Java 99: Automatisez la création de toString ()

Les développeurs travaillant sur de grands projets passent généralement des heures à écrire des toStringméthodes utiles . Même si chaque classe n'obtient pas sa propre toStringméthode, chaque classe de conteneur de données le fera. Permettre à chaque développeur d'écrire à toStringsa manière peut conduire au chaos; chaque développeur proposera sans aucun doute un format unique. En conséquence, l'utilisation de la sortie pendant le débogage devient plus difficile que nécessaire sans aucun avantage évident. Par conséquent, chaque projet doit normaliser sur un format unique pour les toStringméthodes, puis automatiser leur création.

Automatiser toString

Je vais maintenant démontrer un utilitaire avec lequel vous pouvez faire exactement cela. Cet outil génère automatiquement une

toString

méthode pour une classe spécifiée, éliminant presque le temps passé à développer la méthode. Il centralise également le

toString()

format. Si vous modifiez le format, vous devez régénérer le

toString

méthodes; cependant, cela reste beaucoup plus facile que de changer manuellement des centaines ou des milliers de classes.

La maintenance du code généré est également facile. Si vous ajoutez plus d'attributs dans les classes, vous devrez peut-être également apporter les modifications à la toStringméthode. La génération des toStringméthodes étant automatisée, il vous suffit d'exécuter à nouveau l'utilitaire sur la classe pour effectuer vos modifications. C'est plus simple et moins sujet aux erreurs que l'approche manuelle.

Le code

Cet article n'est pas destiné à expliquer l'API Reflection; le code suivant suppose que vous avez au moins une compréhension des concepts derrière Reflection. Vous pouvez visiter le

Ressources

section pour la documentation de l'API Reflection. L'utilitaire s'écrit comme suit:

package tarifé.publications.utilités; import java.lang.reflect. *; public class ToStringGenerator {public static void main (String [] args) {if (args.length == 0) {System.out.println ("Fournissez le nom de la classe comme argument de ligne de commande"); System.exit (0); } essayez {Classe targetClass = Class.forName (args [0]); if (! targetClass.isPrimitive () && targetClass! = String.class) {Champs de champ [] = targetClass.getDeclaredFields (); Classe cSuper = targetClass.getSuperclass (); // Récupération de la sortie de la super classe ("StringBuffer buffer = new StringBuffer (500);"); // Construction du tampon if (cSuper! = Null && cSuper! = Object.class) {output ("buffer.append (super.toString ());"); // toString () de la super classe} for (int j = 0; j <fields.length; j ++) {output ("buffer.append (\" "+ fields [j].getName () + "= \"); "); // Ajouter le nom du champ if (fields [j] .getType (). isPrimitive () || fields [j] .getType () == String.class) // Recherchez une sortie primitive ou chaîne ("buffer.append (this." + Fields [j] .getName () + ");"); // Ajouter la valeur du champ primitif else {/ * Ce n'est PAS un champ primitif donc cela nécessite une vérification de la valeur NULL pour l'objet agrégé * / output ("if (this." + fields [j] .getName () + "! = null)"); output ("buffer.append (this.") + fields [j] .getName () + ".toString ());"); output ("else buffer.append (\" value is null \ ");");} // fin de else} // end de la sortie de la boucle for ("return buffer.toString ();");}} catch (ClassNotFoundException e) {System.out.println ("Classe non trouvée dans le chemin de classe"); System.exit (0);}} sortie vide statique privée (données de chaîne) {System.out.println (données); }}

Le canal de sortie du code

Le format du code dépend également des exigences de votre outil de projet. Certains développeurs peuvent préférer avoir le code dans un fichier défini par l'utilisateur sur le disque. D'autres développeurs sont satisfaits de la

system.out

console, qui leur permet de copier et d'incorporer manuellement le code dans le fichier réel. Je vous laisse simplement ces options et j'utilise la méthode la plus simple:

system.out

déclarations.

Limites de l'approche

Il existe deux limites importantes à cette approche. Le premier est qu'il ne prend pas en charge les objets contenant des cycles. Si l'objet A contient une référence à l'objet B, qui contient alors une référence à l'objet A, cet outil ne fonctionnera pas. Cependant, ce cas sera rare pour de nombreux projets.

La deuxième limitation est que l'ajout ou la soustraction de variables membres nécessite la régénération de la toStringméthode. Comme cela doit être fait avec ou sans l'outil, ce n'est pas un problème spécifique à cette approche.

Conclusion

Dans cet article, j'ai expliqué un petit utilitaire d'automatisation qui peut vraiment améliorer la productivité des développeurs et jouer un rôle petit mais important dans la réduction des délais globaux du projet.


Conseils de suivi

Après la publication de cette astuce, j'ai reçu quelques suggestions de lecteurs sur la façon d'améliorer le code. Dans ce suivi, j'explique comment j'ai mis à jour l'utilitaire en fonction de ces suggestions et de mes propres idées. Vous pouvez trouver le code source de ces améliorations dans Resources.

Amélioration n ° 1, suggérée par Sangeeta Varma

Dans mon code d'origine, je n'ai pas géré les types de tableau pour l'objet et le type de données primitif; le nouveau code gère maintenant les données du tableau. Cependant, le code ne monte que dans les tableaux à dimension unique et ne fonctionnera pas pour les tableaux à dimensions multiples. Je n'ai pas été en mesure de trouver une solution générique à ce problème car, à ma connaissance, il n'y a aucune restriction sur le nombre de dimensions pour les types de données en Java (la seule restriction est la mémoire disponible). Je me réjouis de tout commentaire que vous pouvez offrir pour une solution.

Amélioration n ° 2, suggérée par Chris Sanscraint

À l'origine, j'ai proposé l'utilitaire pour le temps de développement et non pour l'environnement d'exécution. Permettre à l'utilitaire de s'exécuter au moment de l'exécution peut être très pratique, mais peut prendre quelques cycles de processeur supplémentaires. Cependant, le vidage / le débogage d'objets (utilisation de base de toString()) est généralement effectué pendant le temps de développement et est désactivé pour l'environnement de production. Dans certains cas, cette désactivation dans l'environnement de production peut ne pas être applicable car certains projets peuvent l'utiliser toString()à des fins de logique métier. Je suggère de prendre cette décision projet par projet.

Avant de développer cet utilitaire, j'avais déjà en tête cette flexibilité d'exécution. Tout d'abord, j'ai développé une classe de délégation distincte qui a été utilisée par n'importe quelle classe client pour générer le fichier toString(). La classe l'a généré à l'aide d'un appel de méthode comme return ToStringGenerator.generateToString(this), où thispointe vers l'instance actuelle de la classe client et l'instruction de code est écrite dans l' toString()implémentation de la méthode. Mais cette approche a échoué car l'API Reflection n'a pas la capacité d'obtenir les valeurs pour les membres privés au moment de l'exécution. Donc, le cours n'était utile que pour les membres du public, ce que je ne voulais pas.

Mais ensuite, M. Sanscraint a souligné que le même code API Reflection obtient la valeur des membres privés au moment de l'exécution lorsque le code est écrit dans une méthode de la même classe d'appelant. J'ai donc mis à jour l'utilitaire à utiliser lors de l'exécution, et en plus, la toString()méthode n'aura jamais besoin d'être mise à jour ou modifiée pour la soustraction ou l'ajout d'attributs dans la classe cible.

Amélioration n ° 3, suggérée par Eric Ye

À l'origine, j'ai utilisé le thispréfixe pour l'accès aux variables membres dans le code généré, mais M. Ye a souligné que le code peut également être utilisé dans une méthode statique ou même pour générer des membres statiques. Ainsi, le code mis à jour peut désormais gérer à la fois les membres de classe et d'instance. M. Ye a également identifié un bogue, qui a été corrigé dans cette version, qui a amené la classe à générer du code inutile pour les classes sans attribut.

Code modifications

After making the utility runtime-enabled, I was frustrated by having to copy/paste the methods in each class, which became difficult since the new code was comprised of multiple methods.

One solution would be to create an interface/abstract base class that would at least solve the problem of method signatures, but copy/paste would still be required. The abstract base class solution would also restrict the client from deriving from another class.

Une classe interne, cependant, a la capacité d'accéder aux membres privés de la classe parent afin que le code de réflexion, exécuté dans ses méthodes, puisse également obtenir les valeurs privées. J'ai donc décidé de changer l'utilitaire en une classe interne qui pourrait être insérée dans n'importe quelle classe client parent. J'ai également fourni ToStringGeneratorExample.java qui utilise ToStringGenerator.java comme classe interne pour implémenter la toString()méthode.

Enfin, je tiens à remercier les personnes qui ont offert leurs suggestions pour améliorer cette approche.

Syed Fareed Ahmad est un programmeur, concepteur et architecte Java à Lahore, au Pakistan. Il est impliqué dans le développement de solutions e-business basées sur Java (Servlets, JSP et EJB), WebSphere et XML.

En savoir plus sur ce sujet

  • Pour le code source de suivi

    //images.techhive.com/downloads/idge/imported/article/jvw/2000/08/jw-javatip99.zip

  • Documentation de réflexion sur le site Web de Sun

    //java.sun.com/products/jdk/1.1/docs/guide/reflection/index.html

  • Consultez tous les précédents conseils Java et soumettez les vôtres

    //www.javaworld.com/javatips/jw-javatips.index.html

Cette histoire, "Java Tip 99: Automatiser la création de toString ()" a été publiée à l'origine par JavaWorld.