Überlegungen zu Java toString ()

Schon Anfänger von Java-Entwicklern kennen die Nützlichkeit der Object.toString () -Methode, die für alle Instanzen von Java-Klassen verfügbar ist und überschrieben werden kann, um nützliche Details zu einer bestimmten Instanz einer Java-Klasse bereitzustellen. Leider nutzen selbst erfahrene Java-Entwickler diese leistungsstarke Java-Funktion aus verschiedenen Gründen gelegentlich nicht in vollem Umfang. In diesem Blog-Beitrag schaue ich mir das bescheidene Java an toString()und beschreibe einfache Schritte, die man unternehmen kann, um den Utilitarismus von zu verbessern toString().

Explizite Implementierung (Überschreiben) von toString ()

Die vielleicht wichtigste Überlegung im Zusammenhang mit dem Erreichen des Maximalwerts toString()ist die Bereitstellung von Implementierungen. Obwohl der Stamm aller Java-Klassenhierarchien, Object, eine toString () - Implementierung bereitstellt, die allen Java-Klassen zur Verfügung steht, ist das Standardverhalten dieser Methode fast nie nützlich. In Javadoc für Object.toString () wird erläutert, was standardmäßig für toString () bereitgestellt wird, wenn für eine Klasse keine benutzerdefinierte Version bereitgestellt wird:

Die toString-Methode für die Klasse Object gibt eine Zeichenfolge zurück, die aus dem Namen der Klasse, deren Instanz das Objekt ist, dem at-sign-Zeichen "@" und der vorzeichenlosen hexadezimalen Darstellung des Hash-Codes des Objekts besteht. Mit anderen Worten, diese Methode gibt eine Zeichenfolge zurück, die dem Wert von: getClass().getName() + '@' + Integer.toHexString(hashCode())

Es ist schwierig, eine Situation zu finden, in der der Name der Klasse und die hexadezimale Darstellung des durch das @ -Zeichen getrennten Hash-Codes des Objekts nützlich sind. In fast allen Fällen ist es wesentlich nützlicher, eine benutzerdefinierte, explizite anzugeben

toString()

Implementierung in einer Klasse zum Überschreiben dieser Standardversion.

Das Javadoc für Object.toString () sagt uns auch, was eine toString()Implementierung im Allgemeinen beinhalten sollte, und gibt die gleiche Empfehlung ab, die ich hier mache: override toString():

Im Allgemeinen gibt die  toString Methode eine Zeichenfolge zurück, die dieses Objekt "textuell darstellt". Das Ergebnis sollte eine präzise, ​​aber informative Darstellung sein, die für eine Person leicht zu lesen ist. Es wird empfohlen, dass alle Unterklassen diese Methode überschreiben.

Wenn ich eine neue Klasse schreibe, gibt es verschiedene Methoden, die ich als Teil der Erstellung neuer Klassen hinzufügen möchte. Diese schließen ein

Hash-Code()

und

gleich (Objekt)

Falls zutreffend. Nach meiner Erfahrung und meiner Meinung nach jedoch explizit umzusetzen

toString()

ist immer angemessen.

Wenn die "Empfehlung" von Javadoc, dass "alle Unterklassen diese Methode überschreiben", nicht ausreicht (dann gehe ich auch nicht davon aus, dass meine Empfehlung dies ist), um einem Java-Entwickler die Bedeutung und den Wert einer expliziten toString()Methode zu rechtfertigen, empfehle ich, Josh zu überprüfen Blochs effektives Java-Element "Always Override toString" bietet immer zusätzliche Hintergrundinformationen zur Bedeutung der Implementierung toString(). Ich bin der Meinung, dass alle Java-Entwickler eine Kopie von Effective Java besitzen sollten , aber zum Glück ist das Kapitel mit diesem Artikel toString()für diejenigen verfügbar, die keine Kopie besitzen: Methoden, die allen Objekten gemeinsam sind.

Pflegen / Aktualisieren von String ()

Es ist frustrierend, ein Objekt explizit oder implizit toString()in einer Protokollanweisung oder einem anderen Diagnosetool aufzurufen und den Standardklassennamen und den hexadezimalen Hashcode des Objekts zurückzugeben, anstatt etwas Nützlicheres und Lesbareres. Es ist fast genauso frustrierend, eine unvollständige toString()Implementierung zu haben , die keine wesentlichen Teile der aktuellen Eigenschaften und des aktuellen Zustands des Objekts enthält. Ich versuche, diszipliniert genug zu sein und die Gewohnheit zu generieren und zu befolgen, immer die toString()Implementierung equals(Object)sowie die hashCode()Implementierungen und Implementierungen einer Klasse zu überprüfen, an der ich arbeite, die für mich neu ist oder wenn ich die Attribute einer Klasse hinzufüge oder ändere.

Nur die Fakten (aber alle / die meisten von ihnen!)

Im Kapitel von Effective Javazuvor erwähnt, schreibt Bloch: "Wenn dies praktikabel ist, sollte die toString-Methode alle interessanten Informationen zurückgeben, die im Objekt enthalten sind." Es kann schmerzhaft und mühsam sein, alle Attribute einer Attribut-schweren Klasse zu ihrer toString-Implementierung hinzuzufügen, aber der Wert für diejenigen, die versuchen, Probleme im Zusammenhang mit dieser Klasse zu debuggen und zu diagnostizieren, ist die Mühe wert. Normalerweise bemühe ich mich, alle signifikanten Nicht-Null-Attribute meiner Instanz in der generierten String-Darstellung zu haben (und manchmal die Tatsache einzuschließen, dass einige Attribute Null sind). Normalerweise füge ich auch minimalen identifizierenden Text für die Attribute hinzu. Es ist in vielerlei Hinsicht eher eine Kunst als eine Wissenschaft, aber ich versuche, genügend Text einzuschließen, um Attribute zu unterscheiden, ohne zukünftige Entwickler mit zu vielen Details zu belästigen. Das Wichtigste für mich ist, die Attribute zu erhalten.Werte und eine Art Identifizierungsschlüssel vorhanden.

Kenne dein Publikum

Einer der häufigsten Fehler, den Java-Entwickler, die keine Anfänger sind, gemacht haben, toString()ist zu vergessen, wofür und für wen das toString()normalerweise gedacht ist. Im Allgemeinen toString()handelt es sich um ein Diagnose- und Debugging-Tool, mit dem Details zu einer bestimmten Instanz zu einem bestimmten Zeitpunkt für ein späteres Debugging und eine Diagnose einfach protokolliert werden können. Es ist normalerweise ein Fehler, wenn Benutzeroberflächen Zeichenfolgendarstellungen anzeigen, die von einer Darstellung generiert wurden, toString()oder logische Entscheidungen auf der Grundlage einer toString()Darstellung treffen (logische Entscheidungen für eine Zeichenfolge sind in der Tat fragil!). Ich habe gesehen, dass gut gemeinte Entwickler das toString()XML-Format zur Verwendung in einem anderen XML-freundlichen Aspekt des Codes zurückgegeben haben. Ein weiterer schwerwiegender Fehler besteht darin, Clients zu zwingen, den zurückgegebenen String zu analysierentoString()um programmgesteuert auf Datenelemente zuzugreifen. Es ist wahrscheinlich besser, eine öffentliche Getter / Accessor-Methode bereitzustellen, als sich auf die sich toString()nie ändernde Methode zu verlassen . All dies sind Fehler, da diese Ansätze die Absicht einer toString()Implementierung vergessen . Dies ist besonders heimtückisch, wenn der Entwickler wichtige Merkmale aus der toString()Methode entfernt (siehe letztes Element), damit sie auf einer Benutzeroberfläche besser aussieht.

Ich mag toString()Implementierungen, die alle relevanten Details enthalten und eine minimale Formatierung bieten, um diese Details schmackhafter zu machen. Diese Formatierung kann sorgfältig ausgewählte neue Zeilenzeichen [ System.getProperty("line.seperator");] und Tabulatoren, Doppelpunkte, Semikolons usw. enthalten. Ich investiere nicht so viel Zeit wie in ein Ergebnis, das einem Endbenutzer der Software präsentiert wird, aber ich versuche es um die Formatierung so angenehm zu gestalten, dass sie besser lesbar ist. Ich versuche, toString()Methoden zu implementieren , deren Wartung nicht allzu kompliziert oder teuer ist, die jedoch eine sehr einfache Formatierung bieten. Ich versuche, zukünftige Betreuer meines Codes so zu behandeln, wie ich von Entwicklern behandelt werden möchte, deren Code ich eines Tages pflegen werde.

In seinem Artikel zur toString()Implementierung gibt Bloch an, dass ein Entwickler entscheiden sollte, ob die toString()Rückgabe ein bestimmtes Format haben soll oder nicht . Wenn ein bestimmtes Format beabsichtigt ist, sollte dies in den Javadoc-Kommentaren dokumentiert werden, und Bloch empfiehlt ferner, einen statischen Initialisierer bereitzustellen, der das Objekt basierend auf einem von der Zeichenfolge generierten String auf seine Instanzmerkmale zurücksetzen kann toString(). Ich bin mit all dem einverstanden, aber ich glaube, das ist mehr Ärger, als die meisten Entwickler bereit sind zu gehen. Bloch weist auch darauf hin, dass Änderungen an diesem Format in zukünftigen Versionen den Menschen abhängig davon Schmerzen und Angst bereiten werden (weshalb ich es nicht für eine gute Idee halte, dass Logik von einer toString()Ausgabe abhängt ). Mit erheblicher Disziplin, um geeignete Dokumentationen zu schreiben und zu pflegen, mit einem vordefinierten Format für atoString()könnte plausibel sein. Es scheint mir jedoch ein Problem zu sein und besser, einfach eine neue und separate Methode für solche Zwecke zu erstellen und die toString()unbelastet zu lassen.

Keine Nebenwirkungen toleriert

So wichtig die toString()Implementierung auch ist, es ist im Allgemeinen inakzeptabel (und wird sicherlich als schlechte Form angesehen), explizit oder implizit die toString()Auswirkungslogik aufzurufen oder zu Ausnahmen oder Logikproblemen zu führen. Der Autor einer toString()Methode sollte darauf achten, dass Referenzen vor dem Zugriff auf null überprüft werden, um eine NullPointerException zu vermeiden. Viele der Taktiken, die ich im Beitrag Effective Java NullPointerException Handling beschrieben habe, können in der toString()Implementierung verwendet werden. Beispielsweise bietet String.valueOf (Object) einen einfachen Mechanismus für die Nullsicherheit von Attributen fragwürdigen Ursprungs.

Ebenso wichtig ist es für den toString()Entwickler, die Arraygrößen und andere Sammlungsgrößen zu überprüfen, bevor er versucht, auf Elemente außerhalb dieser Sammlung zuzugreifen. Insbesondere ist es allzu einfach, auf eine StringIndexOutOfBoundsException zu stoßen, wenn versucht wird, String-Werte mit String.substring zu bearbeiten.

Da die toString()Implementierung eines Objekts leicht aufgerufen werden kann, ohne dass der Entwickler dies gewissenhaft bemerkt, ist dieser Ratschlag besonders wichtig, um sicherzustellen, dass keine Ausnahmen ausgelöst oder keine Logik ausgeführt werden (insbesondere keine Logik zum Ändern des Zustands). Das Letzte, was jemand möchte, ist, dass das Protokollieren des aktuellen Status einer Instanz zu einer Ausnahme oder Änderung des Status und Verhaltens führt. Eine toString()Implementierung sollte effektiv eine schreibgeschützte Operation sein, bei der der Objektstatus gelesen wird, um einen String für die Rückgabe zu generieren. Wenn dabei Attribute geändert werden, können zu unvorhersehbaren Zeiten schlimme Dinge passieren.

Ich bin der Meinung, dass eine toString()Implementierung nur den Status in den generierten String aufnehmen sollte, auf den zum Zeitpunkt der Generierung im selben Prozessbereich zugegriffen werden kann. Für mich ist es nicht vertretbar, dass eine toString()Implementierung auf Remotedienste zugreift, um den String einer Instanz aufzubauen. Etwas weniger offensichtlich ist vielleicht, dass eine Instanz keine Datenattribute füllen sollte, weil sie toString()aufgerufen wurde. Die toString()Implementierung sollte nur darüber berichten, wie sich die Dinge in der aktuellen Instanz befinden und nicht darüber, wie sie in Zukunft sein könnten oder werden, wenn bestimmte unterschiedliche Szenarien auftreten oder wenn Dinge geladen werden. Um beim Debuggen und bei der Diagnose effektiv zu sein, toString()muss gezeigt werden, wie die Bedingungen sind und nicht wie sie sein könnten.

Einfache Formatierung wird aufrichtig geschätzt

Wie oben beschrieben, kann die umsichtige Verwendung von Zeilentrennzeichen und Tabulatoren hilfreich sein, um lange und komplexe Instanzen beim Generieren im String-Format schmackhafter zu machen. Es gibt andere "Tricks", die die Dinge schöner machen können. Es bietet nicht nur String.valueOf(Object)einen gewissen nullSchutz, sondern wird auch nullals String "null" angezeigt (was häufig die bevorzugte Darstellung von null in einem von toString () generierten String ist. Arrays.toString (Object) ist nützlich, um Arrays einfach als Strings () darzustellen. Weitere Informationen finden Sie in meinem Beitrag Stringifying Java Arrays.

Fügen Sie den Klassennamen in die toString-Darstellung ein

Wie oben beschrieben, stellt die Standardimplementierung von toString()den Klassennamen als Teil der Instanzdarstellung bereit. Wenn wir dies explizit überschreiben, verlieren wir möglicherweise diesen Klassennamen. Dies ist normalerweise keine große Sache, wenn die Zeichenfolge einer Instanz protokolliert wird, da das Protokollierungsframework den Klassennamen enthält. Ich bin jedoch lieber auf der sicheren Seite und habe immer den Klassennamen zur Verfügung. Es ist mir egal, ob die hexadezimale Hash-Code-Darstellung vom Standard abweicht toString(), aber der Klassenname kann nützlich sein. Ein gutes Beispiel hierfür ist Throwable.toString (). Ich bevorzuge diese Methode anstelle von getMessage oder getLocalizedMessage, da erstere ( toString()) den ThrowableKlassennamen des 'enthält, während die beiden letztgenannten Methoden dies nicht tun.

toString () Alternativen

Wir haben dies derzeit nicht (zumindest keinen Standardansatz), aber es wurde über eine Objects-Klasse in Java gesprochen, die einen großen Beitrag zur sicheren und nützlichen Vorbereitung von String-Darstellungen verschiedener Objekte, Datenstrukturen und Sammlungen leisten würde. Ich habe in letzter Zeit keine Fortschritte in JDK7 in dieser Klasse gehört. Eine Standardklasse im JDK, die eine Zeichenfolgendarstellung von Objekten bereitstellt, auch wenn die Klassendefinitionen der Objekte keine explizite Angabe enthalten, toString()wäre hilfreich.

Der Apache Commons ToStringBuilder ist möglicherweise die beliebteste Lösung zum Erstellen sicherer toString () - Implementierungen mit einigen grundlegenden Formatierungssteuerelementen. Ich habe zuvor über ToStringBuilder gebloggt und es gibt zahlreiche andere Online-Ressourcen zur Verwendung von ToStringBuilder.

Der Java Technology Tech-Tipp von Glen McCluskey "Schreiben in String-Methoden" enthält zusätzliche Details zum Schreiben einer guten toString () -Methode. In einem der Leserkommentare gibt Giovanni Pelosi eine Präferenz für die Delegierung der Produktion einer Zeichenfolgendarstellung einer Instanz innerhalb von Vererbungshierarchien von toString()an eine zu diesem Zweck erstellte Delegierungsklasse an.

Fazit

Ich denke, die meisten Java-Entwickler erkennen den Wert guter toString()Implementierungen an. Leider sind diese Implementierungen nicht immer so gut oder nützlich wie sie sein könnten. In diesem Beitrag habe ich versucht, einige Überlegungen zur Verbesserung der toString()Implementierungen zu skizzieren . Obwohl eine toString()Methode die Logik nicht wie eine equals(Object)oder eine Methode beeinflusst (oder zumindest nicht beeinflussen sollte) hashCode(), kann sie das Debuggen und die Diagnoseeffizienz verbessern. Weniger Zeitaufwand für die Ermittlung des Objektzustands bedeutet mehr Zeit für die Behebung des Problems, die Bewältigung interessanterer Herausforderungen und die Befriedigung weiterer Kundenanforderungen.

Diese Geschichte "Java toString () Considerations" wurde ursprünglich von JavaWorld veröffentlicht.