Hallo Leute, ich hätte da mal eine Euch vielleicht seltsam anmutende Frage:
In einem Fachbuch bin ich neulich über die Aussage gestolpert, dass man, im Gegensatz zu den Methoden equals() und hashCode(), toString() nur zu Testzwecken überschreiben sollte. Eine Begründung dafür wurde leider nicht geliefert. Da ich aber dies z.T. sehr intensiv praktiziere, da einige Klassen in JavaFX POJOs bzw. Collections von POJOs erwarten, würde mich mal interessieren, wie die allgemeine Praxis so aussieht. Worauf bezieht sich der Autor mit seiner Behauptung?
Hi,
also ich sehe auch davon ab, toString() zu übeschreiben. Der Grund ist bei mir einfach, dass die Intention hier nicht deutlich wird. Wenn ich ein Objekt zu Text formatieren möchte, biete ich eine Methode dafür an, welche im Namen sprechend erklärt, wie formatiert wird. Angenommen wir haben ein Objekt “Customer”, dann würde ich eine Methode anbieten Customer#formatFullName(), welche mir dann zum Beispiel “Herr Dr. Maximilian Mustermann” ausgibt. Jetzt kann ich noch Customer#formatShortName(), Customer#formatNameAndCity(), usw. usw. anbieten. Das ist eindeutig und jeder weiß was gemeint war.
Erweiterbarkeit wäre auch noch ein Thema. Übeschreibe toString() mit einer von obigen Methoden, wie nennst du die anderen?
Gruß,
Tim
toString ist eine Methode, die jedes Objekt hat, sie kann von “überallher” verwendet werden. Das ist gleichzeitig ein Vorteil wie auch ein Problem:
Verlässt man sich zu sehr auf eine bestimmte Darstellung, kann eine Änderung des Formats böse Folgen haben. In der Praxis reicht das “ein String für alle Gelegenheiten”-Konzept sowieso nicht aus, ich will normalerweise nicht die gleiche Ausgabe in meinem Logfile haben wie in meiner Oberfläche. Oft kommen noch Lokalisierungs- und Internationalisierungsanforderungen dazu. Deshalb sollte man für jeden einzelnen “Verwendungszweck” eine eigene Methode schreiben. Es spricht ja auch nichts dagegen, wenn diese Methoden “erst mal” auf toString verweisen, wenn es passt - Hauptsache man ist später unabhängig, wenn man eine andere Darstellung braucht.
Übrigens wird oft vorausgesetzt, dass die Ergebnisse von toString “stabil” sind, man den zurückgegebenen String z.B. als Schlüssel in einer Map verwenden kann. Das ist aber oft nicht mit anderen Anforderungen vereinbar (Rückgabewert ist etwa locale-abhängig, stammt aus einer Property-Datei oder Datenbank…)
Ok, soweit schon etwas klarer. Ich bin allerdings schon des öfteren, z.B. in Tuts, auf diese Praxis gestoßen. Ich wollte mir eigentlich ersparen, für meine Modellklassen immer einen separaten StringConverter zu bauen. Vielen Dank schon mal.
Der Zweck von toString() ist, eine sinnvolle Ausgabe des internen Zustands des jeweiligen Objekts zu erhalten. Zielgruppe sind Entwickler, die das bspw. für’s Debugging oder beim Lesen von Logausgaben brauchen.
Alle anderen String-Repräsentationen des Objekts sollten nicht über toString gehen, sollten also über extra Methoden zur Verfügung gestellt werden. Diese Methoden gehören aber meiner Meinung nach nicht in die jeweilige Klasse, sondern in extra Klassen. Da greift das Prinzip “Separation of Concerns”. Die Klasse selbst sollte nicht wissen, wie sie bspw. als JSON-String aussieht oder wie sie auf einer GUI dargestellt wird.
Das Überschreiben von toString() für Debugging-Zwecke macht nur bei Klassen Sinn, die einen internen Status haben. Z.B. bei einer Klasse Student macht es also Sinn, bei einer Comparator-Implementierung eher weniger.
Kann nillehammer nur zustimmen. toString ist die Darstellung der Klasse in ihrem aktuellen Zustand. Nützlich, um sie sich beim Debugen auszugeben oder ins Log-File zu schreiben. Für bestimmte String-Darstellungen würde ich auch eigenen Methode oder sogar Klassen (nach bspw. JSON oder XML) verwenden.
Daher: toString() ruhig überschreiben, aber sich nicht auf die Implementierung verlassen. Und erst recht nicht in Produktiv-Code aktiv einsetzen.
Noch ein „+1“ dazu: Das „toString“, das standardmäßig von IDEs generiert wird, ist IMHO OK, nach dem Muster
NameDerKlasse[nameVonField0=wertVonField0,…,nameVonFieldN=wertVonFieldN]
Wenn man das ausgibt, weiß man, was Sache ist.
Ansonsten sollte man sich auf NICHTS verlassen, was toString liefert. Also: Nicht den String in eine Datei schreiben und einen Parser dafür erstellen, um es zu Persistenzzwecken zu verwenden :o) (Naja… in Anbetracht der Tatsache, dass ich schon Dinge gesehen haben, die dem erstaunlich nahe kommen (!), wäre eher ein angebracht…)
Allerdings habe ich schon gelegentlich vom (nachvollziehbaren!) Wunsch gehört, ein Überschreiben von „toString“ erzwingen zu können, um sicherzustellen, dass ein Objekt, das im GUI (z.B. in einer JList) standardmäßig über seinen toString-Wert repräsentiert wird, dort erwas „sinnvolles“ liefert. In der Praxis ist das aber nicht sinnvoll, aus den schon genannten Gründen (Internationalisierung, mangelnde Flexibilität etc)
[QUOTE=Marco13]Noch ein „+1“ dazu: Das „toString“, das standardmäßig von IDEs generiert wird, ist IMHO OK, nach dem Muster
NameDerKlasse[nameVonField0=wertVonField0,…,nameVonFieldN=wertVonFieldN]
Wenn man das ausgibt, weiß man, was Sache ist.
[/QUOTE]
na hoffentlich ist der wertVonField0 nicht auch wieder toString() (was sonst allerdings?),
und zwar von einer Klasse die wiederum ein Eclipse-toString() mit gleichen Aufbau hat,
wenn sich dann zwei Objekte solcher Klassen gegenseitig referenzieren gibt es einen schönen StackOverflowError
edit: kurz getestet, kommt hin (also zum Error)
public class Test {
public static void main(String[] args) {
System.out.println(new A());
}
}
class A {
B b = new B(this);
public String toString() { return "A [b=" + b + "]"; }
}
class B {
A a;
B(A a) { this.a = a; }
public String toString() { return "B [a=" + a + "]"; }
}
das Default-toString() reicht für den Normalfall,
wann immer man genaueres braucht, eine Klasse näher kennenlernen will, dürfe das Anlass genug sein, ein eigenes toString() zu schreiben und sogar Neukompilieren/ Neustarten usw.,
so oft kommt das hoffentlich nicht vor, rechtfertigt gewissen Aufwand
der Fehler kann dabei freilich genauso passieren,
ich persönlich nehme lieber extra Methoden, getDescription(), nur für inidividuell gesetzte Ausgaben,
um in der API (z.B. bei Exceptions, aber auch Ausgabe von Map, List & Co.) evtl. aufgerufenes toString() in Frieden zu lassen,
aber auch dann kann es weiterhin dazu und zu anderen Problemen kommen
[quote=nillehammer]Der Zweck von toString() ist, eine sinnvolle Ausgabe des internen Zustands des jeweiligen Objekts zu erhalten.[/quote]Leider haben die Entwickler von (J)Table und (J)List es sich auch einfach genmacht und verwenden die Ausgabe von toString() standardmäßig für ihre Anzeige. Damit ist eine “fachlich orientierte” Implementierung dieser Methode der einfachste Weg, eine sinnvolle Anzeigen zu bekommen.
bye
TT
Das zwei Objekte sich so gegenseitig kennen empfinde ich als SO unschön, dass ich diesen Fall mal nicht berücksichtigt habe. Und wer das macht, der hat es verdient, einen StackOverflowError zu bekommen
(Im Ernst, das versuche ich i.a. zu vermeiden, wenn es um unterschiedliche Objektarten geht. Es gibt keine klare Abhängigkeitsrichtung mehr. Man kann das eine Objekt nicht sinnvoll (d.h. ggf nicht in einem „gültigen“ Zustand) erstellen, solange es das andere nicht gibt - und umgekehrt! Natürlich gibt es Grenzfälle…
class VerheirateterKatholik { private VerheirateterKatholik partner; ... }
aber… im allgemeinen, eben )
@Marco13
Problem besteht auch über weitere Weg, etwa A mit einer Liste von Bs, B zeigt auf seinen ‚Parent‘, A,
die Liste als Bindeglied spielt da schon automatisch mit
doppelt verkettete Liste, Baumstrukturen
man denke nur an Hibernate-Mappings,
SQL ist redundanzfrei, Fremdschlüssel nur in einer Tabelle,
aber es ist doch gerade einer der Vorteile der schönen Objekt-Ebene, dass man viel mehr abbilden kann,
alle verweisen untereinander, Objekt-Graph (mit Kreisen )
An object graph is a directed graph, which might be cyclic.
puh, in den kaum 6 Zeilen des Artikels (edit: auf meinem neuen Breit-Monitor) zum Glück auch cyclic dabei wenn auch mit Einschränkung (might)
Sicher kommt das vor, und ich hatte diesen StackOverflowError auch schon (genaugenommen kann man bei der Implementierung einer Klasse (bzw. ihrer toString-Methode) ggf. gar nicht wissen, ob so ein Zyklus bestehen kann!). Aber … was ist eine sinnvolle Alternative? Die Object#toString taugt bestenfalls als “Identifikator”, wenn man toString aufruft dann erwartet man meistens mehr als ClassName+HashCode. Im GUI sind die Erwartungen ggf. sehr speziell (so speziell, dass man bei ihrer Erfüllung IMHO nicht auf toString vertrauen sollte), aber in den übrigen Fällen finde ich Infos über die Fields eben am praktischsten.
wie gesagt hat man meiner Meinung nach gar nicht so viele Klassen, als dass man sich nicht jeweils die 15 Min. Zeit nehmen könnte eine toString() selber zu schreiben,
da gibt man das aus was wichtig ist, die Id und den Namen des eigenen Objektes, vielleicht ausgesuchte weitere Werte,
wenn ein Objekt-Parameter b noch wichtig ist, dann wiederum von diesem (neben null-Prüfung (*)) ein bestimmtes Feld wie Id oder Name, nicht toString() mit selber 100 Zeichen
insofern kurze Aussage in langen Postings: generisch erstelltes toString() ist recht zweifelhaft
(*) es gibt ja auch die Fraktion, die gar nie null als Wert erlaubt, da ergeben sich hier auch schöne Verwicklungen, aber nun ist mal gut
spätes edit: wenn man sich eine Klasse 35 sec anschaut, alle Felder als harmlos und relevant oder zumindest nicht störend ansieht,
kann man sich akzeptablen Code dazu natürlich auch mit einem Mausklick holen und dies mit gutem Gewissen (oder kleinen Bearbeitungen) freigeben,
muss nicht zwingend selber programmiert werden,
aber ungesehen/ automatisch in jeder Klasse, besser nicht
Oder nur, wenn man im gleichen Zug auch automatisch auf “Generate all getters and setters” klickt :o) Natürlich nicht unüberlegt, es ging ja nur um das allgemeine Muster…
Für was benötigt man normalerweise die toString Funktion? Für DTOs. Und da ist es mMn auch kein Kapitalverbrechen, wenn man die toString sinnvoll überschreibt und wenn man sich dadurch irgendeinen CellRenderer sparen kann umso besser. ToString für irgendwelche höheren Zwecke zu missbrauchen wie oben angedeutet (Persistenz) is natürlich Unfug.
Vielen Dank noch mal an Euch für die anregende Diskussion und die teilweise doch recht “witzigen” Beispiele. Ich ziehe für mich daraus folgenden Schluss: Eigentlich sollte man es nicht machen, da an jeder Ecke potentielle Gefahren lauern, aber man sollte es von Fall zu Fall prüfen. Da ich zum (intensiven) Prüfen meist zu faul bin, werde ich diese Methode erst mal nicht mehr überschreiben.
da ich extra gepostet hatte weil zuvor kaum von Gefahr die Rede war, beziehe ich jetzt ‚an jeder Ecke potentielle Gefahren lauern‘ vornehmlich auf mein Beispiel,
das ist bisschen zu viel des Guten/ Schlechten,
es gibt theoretisch denkbare Gefahren, aber ein Minenfeld zu skizzieren, Abkehr davon, war nicht meine Intention
ich nehme das Posting zuletzt dennoch als Anlass, dass Thema auf Geölst zu setzen, immer schöner als offen
(edit: noch ne Antwort nach mir, dann vorerst nicht…)
Die Schlussfolgerung ist nicht ganz richtig. Es wird sogar ausdrücklich empfohlen:
(s. Object#toString())
Nur nicht zu solchen Zwecken wie man es oft (auch in Tutorials) sieht und Du es vermutlich bisher gemacht hast. Sondern eher für Logging o.ä. um relevante Informationen kurz und knapp zur Verfügung zu stellen.
The result should be a concise but informative representation that is easy for a person to read
Ich würde jetzt allerdings nicht bei jeder eigenen Klasse die toString() überschreiben, ich selbst mache das eher bei Klassen die Datenobjekte beschreiben.
Ich glaube nicht, dass es sich die Entwickler der Swing-Klassen einfach machen wollten. Ich glaube eher, dass sie den Aufruf von toString() einfach als „letze Möglichkeit, wenn nix mehr geht“ eingebaut haben. Ist natürlich nur Spekulation. Habe das aber auch schon in anderen Frameworks für Conversion/Coercion gesehen. Auch wenn so ein Vorgehen zum Missbrauch von toString() verführt, finde ich es dennoch ok, seine Frameworks so zu bauen.
Und speziell bei Swing: Du als erfahrener Swing-Entwickler wirst sicher auch wissen, dass es zur Separation von Concerns das Konzept der Renderer gibt. Einen solchen zu implementieren macht auch nicht wirklich viel Arbeit. Und nur weil einfach geht, den Sinn von toString() sowie sämtliche Swing-Torials zu missachten, ist für mich keine Entschuldigung. Das würde ich ähnlich einstufen, wie hashCode() für die generierung von ids zu benutzen (ich weiß, das macht keiner, wollte aber ein absurdes Beispiel nenne, um klar zu machen, wie mies ich den Missbrauch von toString finde).
[QUOTE=nillehammer]
Und speziell bei Swing: Du als erfahrener Swing-Entwickler wirst sicher auch wissen, dass es zur Separation von Concerns das Konzept der Renderer gibt. Einen solchen zu implementieren macht auch nicht wirklich viel Arbeit. Und nur weil einfach geht, den Sinn von toString() sowie sämtliche Swing-Torials zu missachten, ist für mich keine Entschuldigung. Das würde ich ähnlich einstufen, wie hashCode() für die generierung von ids zu benutzen (ich weiß, das macht keiner, wollte aber ein absurdes Beispiel nenne, um klar zu machen, wie mies ich den Missbrauch von toString finde).[/QUOTE]
die DefaultTableCellRenderer-Klasse ist 300 Zeilen lang, benutzt auf komplizierte unschöne Weise JLabel mit überschriebenen Methoden,
protected void setValue(Object value) {
setText((value == null) ? "" : value.toString());
}
ist dabei eine immerhin abgetrennte Methode, wenn man die überschreiben würde hätte man die Kontrolle über diese Teilaufgabe
aber ist je in offiziellen Tutorials der Vorschlag gehört, setValue() von DefaultTableModel zu überschreiben?
das TableCellRenderer-Interface hat nur getTableCellRendererComponent(), Augenmerk auf das komplette komplizierten Rendern mit eben 300 Zeilen-Klassen als Konsequenz
insofern ist es etwas weit hergeholt zu behaupten, Swing würde sich darum kümmern/ mit Renderer ein passendes Konzept anbieten,
toString() wird dabei so gut wie gar nicht berührt, fällt eher zufällig bei einer Default-Implementierung in kleinen Focus
danach erst nachgeschaut, das passt ja:
bei JList/ DefaultListCellRenderer gibt es nichtmal die setValue-Methode,
in der komplizierten Haupt-Methode steht direkt drin
...
if (value instanceof Icon) {
setIcon((Icon)value);
setText("");
}
else {
setIcon(null);
setText((value == null) ? "" : value.toString());
}
...
da muss man nun unschön den ganzen Code kopieren,
oder gar die Arbeit jeweils auf das Model, auf deren Methode getValueAt() verlegen, auch unklare Unterscheidung,