Java-Tipp 99: Automatisieren Sie die Erstellung von toString ()

Entwickler, die an großen Projekten arbeiten, verbringen normalerweise Stunden damit, nützliche toStringMethoden zu schreiben . Selbst wenn jede Klasse keine eigene toStringMethode erhält , wird jede Datencontainerklasse dies tun. Wenn jeder Entwickler toStringseinen eigenen Weg schreiben kann, kann dies zu Chaos führen. Jeder Entwickler wird zweifellos ein einzigartiges Format entwickeln. Infolgedessen wird die Verwendung der Ausgabe während des Debuggens ohne offensichtlichen Vorteil schwieriger als nötig. Daher sollte jedes Projekt ein einziges Format für toStringMethoden standardisieren und dann deren Erstellung automatisieren.

ToString automatisieren

Ich werde jetzt ein Dienstprogramm demonstrieren, mit dem Sie genau das tun können. Dieses Tool generiert automatisch eine regelmäßige und robuste

toString

Methode für eine bestimmte Klasse, wodurch der Zeitaufwand für die Entwicklung der Methode nahezu entfällt. Es zentralisiert auch die

toString()

Format. Wenn Sie das Format ändern, müssen Sie das neu generieren

toString

Methoden; Dies ist jedoch immer noch viel einfacher als das manuelle Ändern von Hunderten oder Tausenden von Klassen.

Die Pflege des generierten Codes ist ebenfalls einfach. Wenn Sie den Klassen weitere Attribute hinzufügen, müssen Sie möglicherweise auch die Änderungen an der toStringMethode vornehmen . Da die Generierung von toStringMethoden automatisiert ist, müssen Sie das Dienstprogramm für die Klasse nur erneut ausführen, um Ihre Änderungen vorzunehmen. Dies ist einfacher und weniger fehleranfällig als der manuelle Ansatz.

Der Code

Dieser Artikel soll die Reflection-API nicht erläutern. Der folgende Code setzt voraus, dass Sie mindestens die Konzepte hinter Reflection verstehen. Sie können die besuchen

Ressourcen

Abschnitt für die Dokumentation der Reflection-API. Das Dienstprogramm ist wie folgt geschrieben:

Paket preis.publications.utilities; import java.lang.reflect. *; öffentliche Klasse ToStringGenerator {public static void main (String [] args) {if (args.length == 0) {System.out.println ("Geben Sie den Klassennamen als Befehlszeilenargument an"); System.exit (0); } try {Class targetClass = Class.forName (args [0]); if (! targetClass.isPrimitive () && targetClass! = String.class) {Feldfelder [] = targetClass.getDeclaredFields (); Klasse cSuper = targetClass.getSuperclass (); // Abrufen der Superklassenausgabe ("StringBuffer buffer = new StringBuffer (500);"); // Pufferkonstruktion if (cSuper! = Null && cSuper! = Object.class) {output ("buffer.append (super.toString ());"); // Superklasse toString ()} für (int j = 0; j <fields.length; j ++) {output ("buffer.append (\" "+ fields [j]).getName () + "= \"); "); // Feldname anhängen if (Felder [j] .getType (). isPrimitive () || Felder [j] .getType () == String.class) // Suchen Sie nach einer Grund- oder Zeichenfolgenausgabe ("buffer.append (this." + Fields [j] .getName () + ");"); // Hängen Sie den Wert des Grundfelds an, sonst {/ * Es ist also KEIN Grundelement Dies erfordert eine Überprüfung des NULL-Werts für das aggregierte Objekt * / output ("if (this." + fields [j] .getName () + "! = null)"); output ("buffer.append (this"). + fields [j] .getName () + ".toString ());"); output ("else buffer.append (" value is null ");");} // end of else} // end of for Schleifenausgabe ("return buffer.toString ();");}} catch (ClassNotFoundException e) {System.out.println ("Klasse nicht im Klassenpfad gefunden"); System.exit (0);}} private statische void-Ausgabe (String-Daten) {System.out.println (Daten); }}

Der Code-Ausgabekanal

Das Format des Codes hängt auch von den Anforderungen Ihres Projektwerkzeugs ab. Einige Entwickler bevorzugen möglicherweise den Code in einer benutzerdefinierten Datei auf der Festplatte. Andere Entwickler sind mit dem zufrieden

system.out

Konsole, mit der sie den Code manuell kopieren und in die eigentliche Datei einbetten können. Ich überlasse Ihnen diese Optionen einfach und verwende die einfachste Methode:

system.out

Aussagen.

Einschränkungen des Ansatzes

Dieser Ansatz weist zwei wichtige Einschränkungen auf. Das erste ist, dass es keine Objekte unterstützt, die Zyklen enthalten. Wenn Objekt A einen Verweis auf Objekt B enthält, der dann einen Verweis auf Objekt A enthält, funktioniert dieses Tool nicht. Dieser Fall wird jedoch bei vielen Projekten selten sein.

Die zweite Einschränkung besteht darin, dass das Hinzufügen oder Subtrahieren von Elementvariablen eine Regeneration der toStringMethode erfordert . Da dies mit oder ohne Tool erfolgen muss, ist dies kein spezifisches Problem für diesen Ansatz.

Fazit

In diesem Artikel habe ich ein kleines Automatisierungsprogramm erläutert, das die Entwicklerproduktivität wirklich verbessern und eine kleine, aber wichtige Rolle bei der Reduzierung der Gesamtprojektzeitpläne spielen kann.


Follow-up-Tipps

Nachdem dieser Tipp veröffentlicht wurde, erhielt ich einige Vorschläge von Lesern, wie der Code verbessert werden kann. In diesem Follow-up erkläre ich, wie ich das Dienstprogramm basierend auf diesen Vorschlägen und meinen eigenen Erkenntnissen aktualisiert habe. Den Quellcode für diese Verbesserungen finden Sie unter Ressourcen.

Verbesserung Nr. 1, vorgeschlagen von Sangeeta Varma

In meinem ursprünglichen Code habe ich die Array-Typen für das Objekt und den primitiven Datentyp nicht behandelt. Der neue Code verarbeitet jetzt die Array-Daten. Der Code gilt jedoch nur für Arrays mit einer Dimension und funktioniert nicht für Arrays mit mehreren Dimensionen. Ich konnte keine generische Lösung für dieses Problem finden, da nach meinem besten Wissen die Anzahl der Dimensionen für Datentypen in Java nicht eingeschränkt ist (die einzige Einschränkung ist der verfügbare Speicher). Ich freue mich über jedes Feedback, das Sie für eine Lösung geben können.

Verbesserung Nr. 2, vorgeschlagen von Chris Sanscraint

Ursprünglich schlug ich das Dienstprogramm für die Entwicklungszeit und nicht für die Laufzeitumgebung vor. Das Ausführen des Dienstprogramms zur Laufzeit kann sehr praktisch sein, kann jedoch einige weitere CPU-Zyklen dauern. Das Objekt-Dumping / Debugging (grundlegende Verwendung von toString()) wird jedoch normalerweise während der Entwicklungszeit durchgeführt und für die Produktionsumgebung ausgeschaltet. In einigen Fällen ist dieses Ausschalten in der Produktionsumgebung möglicherweise nicht anwendbar, da einige Projekte möglicherweise toString()für Zwecke der Geschäftslogik verwendet werden. Ich schlage vor, diese Entscheidung von Projekt zu Projekt zu treffen.

Vor der Entwicklung dieses Dienstprogramms hatte ich bereits diese Laufzeitflexibilität im Kopf. Zuerst habe ich eine separate delegierende Klasse entwickelt, die von jeder Client-Klasse zum Generieren der verwendet wurde toString(). Die Klasse hat es mit einem Methodenaufruf wie generiert return ToStringGenerator.generateToString(this), wobei thisauf die aktuelle Instanz der Client-Klasse verweist und die Code-Anweisung in die toString()Methodenimplementierung geschrieben wird. Dieser Ansatz ist jedoch fehlgeschlagen, da die Reflection-API zur Laufzeit nicht in der Lage ist, die Werte für die privaten Mitglieder abzurufen. Die Klasse war also nur für öffentliche Mitglieder nützlich, was ich nicht wollte.

Aber dann wies Herr Sanscraint darauf hin, dass derselbe Reflection-API-Code zur Laufzeit den Wert der privaten Mitglieder erhält, wenn der Code innerhalb einer Methode derselben Aufruferklasse geschrieben wird. Daher habe ich das zur Laufzeit zu verwendende Dienstprogramm aktualisiert. Außerdem muss die toString()Methode niemals aktualisiert oder bearbeitet werden, um Attribute in der Zielklasse zu subtrahieren oder hinzuzufügen.

Verbesserung Nr. 3, vorgeschlagen von Eric Ye

Ursprünglich habe ich das thisPräfix für den Zugriff auf Mitgliedsvariablen im generierten Code verwendet, aber Herr Ye wies darauf hin, dass der Code auch in einer statischen Methode oder sogar zur Ausgabe statischer Mitglieder verwendet werden kann. Der aktualisierte Code kann jetzt sowohl Klassen- als auch Instanzmitglieder verarbeiten. Herr Ye identifizierte auch einen Fehler, der in dieser Version behoben wurde und dazu führte, dass die Klasse nutzlosen Code für attributlose Klassen generierte.

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.

Eine innere Klasse kann jedoch auf die privaten Mitglieder der übergeordneten Klasse zugreifen, sodass der Reflektionscode, der innerhalb ihrer Methoden ausgeführt wird, auch die privaten Werte abrufen kann. Deshalb habe ich beschlossen, das Dienstprogramm in eine innere Klasse zu ändern, die in jede übergeordnete Clientklasse eingefügt werden kann. Ich habe auch ToStringGeneratorExample.java bereitgestellt, das ToStringGenerator.java als innere Klasse zum Implementieren der toString()Methode verwendet.

Abschließend möchte ich mich bei den Personen bedanken, die ihre Vorschläge zur Verbesserung dieses Ansatzes gemacht haben.

Syed Fareed Ahmad ist ein Java-Programmierer, Designer und Architekt in Lahore, Pakistan. Er ist an der Entwicklung von Java- (Servlets, JSP und EJB), WebSphere- und XML-basierten E-Business-Lösungen beteiligt.

Erfahren Sie mehr über dieses Thema

  • Für den nachfolgenden Quellcode

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

  • Reflexionsdokumentation auf der Sun-Website

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

  • Zeigen Sie alle vorherigen Java-Tipps an und senden Sie Ihre eigenen

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

Diese Geschichte "Java-Tipp 99: Automatisieren der Erstellung von toString ()" wurde ursprünglich von JavaWorld veröffentlicht.