Wann toString() überschreiben?

babuschka

Top Contributor
Hallo, also ich weiß, daß alle Klasse von der Klasse Object die Methode toString() erben und daß man in gewissen Situationen diese Methode an die jeweilige Klasse anpassen muss.


Was sind solche Situationen, wo man das muss? Ich habe mir mal ein Beispiel ausgedacht.

Wenn ich zum Beispiel eine neue Klasse "Katzen" schreibe, wie folgt:

Java:
class Katzen{

String name;
int age;

public Katzen(String name, int age){

  this.name= name;
  this.age = age;
}

public static void main( String[] args ){

 Katzen felix = new Katzen("Felix", 2);

System.out.println(felix);
}
}

So wird mir
Code:
Katzen@19821f
ausgegeben, okay.

Ist das Überschreiben jetzt so gemeint, daß ich in der Klasse "Katzen" die Methode
Java:
public String toString() {
  return name;
}

schreibe und dann statt Zeile 16 schreibe:

Java:
System.out.println(felix.toString() );
?

Oder habe ich die Funktionen bzw. den Sinn des Überschreibens missverstanden, denn man könnte ja auch einfach Zeile 16 ersetzen durch:

Java:
System.out.println(felix.name);

und wozu dann das Überschreiben und die Mühe, die Methode zu schreiben?
 
Die MEthode toString() kannst du als Shortcut verwenden, also eine Art Standard-Methode. Wann immer also eine Ausgabe irgendwo machst, kannst du statt felix-toString() auch einfach felix schreiben...

In deinem Fall also:
Java:
System.out.println(felix);

EDIT: Mit toString() lieferst du laut JAVA-API eine String-Representation deiner Instanz zurück, also irgendetwas, was dem Objekt ´nen Namen gibt oder sonstwelche Informationen. So genau ist das nicht definiert, du kannst da auch "Katzenfutter" hinschreiben...
 
Zuletzt bearbeitet:
Na ja, so ein Object kann ja mehr als zwei properties haben.

Und wenn du jetzt von einer Katze, den Namen, das Alter, das Geschlecht, die Felllänge, die Farbe, die Größe und das Gewicht entsprechend ausgeben willst ... macht das Überschreiben der toString methode schon sinn.
 
Okay, wenn ich die Methode toString überschrieben habe, kann ich mir das .toString() also sparen.


Aber meine Frage war, ob ich den Sinn des Überschreibens richtig verstanden habe und wieso man sich die Mühe macht, wenn man auch felix.name nehmen kann,
 
Was ausprobieren?

Habe ich doch schon.

Wenn ich das mit dem Überschreiben weglasse und stattdessen

Code:
System.out.println(felix.name);

schreibe, wird der Name auch ausgegeben.


Daher ja meine Frage, wieso man das mit dem Überschreiben eigentlich macht...
 
Was sind solche Situationen, wo man das muss?
Die gibt es nicht ;-) Man muss die Methode nicht überschreiben.
Ist das Überschreiben jetzt so gemeint, daß ich in der Klasse "Katzen" die Methode
Java:
public String toString() {
  return name;
}
Ja
denn man könnte ja auch einfach Zeile 16 ersetzen durch:

Java:
System.out.println(felix.name);
Das wäre besser. Noch besser wäre allerdings, wenn man nur über getter und setter auf die Variable zugreifen kann, es also eine Methode getName() gibt, die den Namen zurück gibt.
und wozu dann das Überschreiben und die Mühe, die Methode zu schreiben?
Man kann die Methode überschreiben, um z.B. den Zustand eines Objektes zurück zugeben (Debugging, Logging...)
Java:
public String toString() {
    return this.getClass().getName() + "(Name: " + name + ", Age: " + age + ")";
}
Ein Fall bei dem man das Überschreiben der Methode "missbrauchen" kann ist, im Zusammenhang mit GUI Komponenten (JList, JTable, JTree...) diese verwenden standardmäßig ebenfalls die toString(), um das Objekt darzustellen. Hiermit erspart man sich das programmieren eines Renderers.
 
Es gibt Situationen, in denen man ein [c]toString()[/c] nicht einfach mit einem [c]System.out.println(felix.name);[/c] ersetzen kann. Du könntest für deine Katze(n) folgende Methode schreiben:
Java:
public String toString() {
    return name + ", " age + (age == 1 ? " Jahr" : " Jahre");
Was dann bei
Java:
List<Katze> katzen = ...;
for (Katze katze : katzen) {
    System.out.println(katze);
}
sowas ausgeben würde
Code:
Friedolin, 2 Jahre
Schnurri, 1 Jahr
Hanswurst, 3 Jahre
 
Ist es gut ein Automatismus für die toString zu entwickeln, so dass man die toString() immer per rat hat als Clean code ? So das man die toString immer mit der Anpassung der Klasse mit anpasst?
 
Schwer zu sagen. Es erhöht den Wartungsaufwand, wenn du immer die toString Methode anpassen musst, wenn du deine Klasse änderst. Und wenn du die Objekte nie ausgibst, dann ist der Nutzen minimal, aber der Aufwand erhöht. Und wenn man es nicht konsequent pflegt und mit Tests absichert, ist wenn du es mal brauchst, dann garantiert die Methode toString() nicht mehr synchron zur Klasse.

Ich hab mir für ein privates Projekt was per Reflection gebastelt:
Java:
public class GenericToString {

	private static final Logger logger = LoggerFactory.getLogger(GenericToString.class);

	public static String toString(Object o) {
		String result = o.getClass().getSimpleName() + " [";
		Field[] fields = o.getClass().getDeclaredFields();
		return result + Arrays.stream(fields).map(f -> mapField(o, f)).collect(Collectors.joining(", ")) + "]";
	}

	private static String mapField(Object o, Field f) {
		String valueString = getFieldValue(o, f);
		return f.getName() + "=\"" + valueString + "\"";
	}

	private static String getFieldValue(Object o, Field f) {
		try {
			f.setAccessible(true);
			Object value = f.get(o);
			if (value == null) {
				return null;
			} else {
				return value.toString();
			}
		} catch (IllegalArgumentException | IllegalAccessException e) {
			logger.warn("Error while accessing field " + f, e);
			return null;
		}
	}

Dann kann ich toString() einfach so überschreiben:
Java:
	@Override
	public String toString() {
		return GenericToString.toString(this);
	}

Ob man das aber wieder will, ist auch eine Sache - damit gebe ich den kompletten Objektzustand nach außen über die toString Methode. Das ist je nach Anforderung auch nicht gewünscht.

Von daher ist meine Meinung, es gibt keine einfache Antwort auf die Frage.
 
Ich hab mir für ein privates Projekt was per Reflection gebastelt:

Also ich würde da nicht zu Reflection greifen sondern mir den Code eher von der IDE generieren lassen (Dann muss man ihn aber bei Änderungen anpassen - aber man kann ihn jederzeit anpassen) oder die Generierung beim Compile-Lauf durchführen lassen (Project Lombok).

Ich persönlich greife immer sehr gerne zu Lombok.
 
@LimDul ja gut, aber Putzt du als Hausmeister oder wer das mach, nicht den Treppenflur, nur weil du den Fußabtreter immer zu Seite packen musst? Es muss zur Firmen Kultur(Clean Code Vereinbarung> gehören ,dass immer die toString anzupassen mit jeder Klasse Änderung und außerdem so wie @JustNobody sagt Generieren IDE's schon eine toString(). Also ich glaube das ist ein Aufwand von nicht mal 2min sogar noch weniger, was nach hinten wider aufgefangen wird.
 
Es erstaunt mich, dass das hier noch nicht erwähnt wurde (EDIT: ups, habe gerade den Satz von @JustNobody überlesen... sorry!) , aber hierfür gibt es https://projectlombok.org/. Einfach als Standard für jedes Projektsetup einführen (ist bei uns so) und du hast keine Probleme mehr mit korrekten toString(), hashCode(), equals(), gettern, settern, constructoren, builder-Klassen, ... nichts muss manuell geschrieben/maintained oder "semi-manuell" per IDE generiert werden.
Ein ähnliches Ziel verfolgt auch: https://openjdk.java.net/jeps/359
 
Also das Problem ist aus meiner Sicht, dass bei manueller Anpassung (Und wenn es nur ein klick in der IDE ist, da etwas zu generieren) es früher oder später zu Fehlern kommen wird. So menschliche Fehler sind schlicht unnötig und daher ist es aus meiner Sicht relativ fatal, hier auf den reinen Faktor Mensch zu setzen.

Daher ist da aus meiner Sicht Lombok mit die beste Wahl.

Aber generell frage ich mich doch, was hier der toString Methode für eine Bedeutung beigemessen wurde. Wo wird denn toString wirklich benutzt in komplexen Klassen? Wenn ich da jetzt etwas zurück denke, dann ist das eigentlich nur eine Sache für Logfiles....
Denn sobald man eine Applikation schreibt, dann wird man doch das Model von der View trennen. Dabei ist es jetzt egal, wie man das nun genau auftrennt und wie man die Teile beschimpft: MVC, MVVM, ... Eine sinnvolle Aufteilung wird hoffentlich erfolgen...
 
Wo hier gerade Lombok so positiv betrachtet wird möchte ich aber auch Kritik anbringen:

Das ganze Projekt ist nicht per se gut und sollte als Ganzes eingesetzt werden!

Es gibt Teile, die ich sehr positiv sehe:
@Getter, @Setter, @Data, @Builder, @EqualsHashCode, @ToString

Es gibt Teile, die ich neutral sehe:
@Log-Variationen z.B. - Ob ein @Slf4j an einer Klasse besser ist als eine Zeile private static final Logger log = .... muss jeder für dich beantworten ...

Es gibt Teile, die komplett ablehne und deren Einsatz in meinen Augen nicht zu rechtfertigen ist:
@Cleanup ist hier das beste Beispiel. Mit Java 8 gibt es das try with resources und damit für so ein Feature keine Rechtfertigung mehr!

Viele Dinge habe ich noch nicht genannt. Jeder kann sich da seine eigenen Gedanken machen. Aber ich würde generell immer erst schauen, ob es nicht andere Lösungen gibt....

Und wenn Lösungen kommen, dann sollte man sich diese auch sehr gut ansehen - @httpdigest hat z.B. auf die Records hingewiesen. Hier muss jeder für sich entscheiden, ob sowas nicht ein Ersatz für bisherige @Data Einsätze sein kann und was hier evtl. bei der eigenen Art und Weise angepasst werden sollte. Das Stichwort immutable ist hier wichtig und interessant und evtl. ist man da ja auch an anderer Stelle drüber gestolpert wie z.B. bei (parallel) Streams, wo dies wichtig sein könnte um Laufzeitprobleme zu verhindern ... Aber das führt jetzt zu weit in ein anderes Thema, das ich hier jetzt nicht weiter vertiefen möchte.
 
@LimDul ja gut, aber Putzt du als Hausmeister oder wer das mach, nicht den Treppenflur, nur weil du den Fußabtreter immer zu Seite packen musst? Es muss zur Firmen Kultur(Clean Code Vereinbarung> gehören ,dass immer die toString anzupassen mit jeder Klasse Änderung und außerdem so wie @JustNobody sagt Generieren IDE's schon eine toString(). Also ich glaube das ist ein Aufwand von nicht mal 2min sogar noch weniger, was nach hinten wider aufgefangen wird.
Klar. Von der reinen Lehre hast du Recht.

Aber ich hab mittlerweile genug Erfahrung um zu wissen:
* Fehler passieren.
* Bei Dingen, wo der Nutzen schwerer vermittelbar ist (was bei einer toString Mehode der Fall ist, wenn aufgrund der Anwendung das quasi nie relevant ist, weil nie Objekte mittels toString ausgegeben werden) denken die Leute automatisch weniger dran, bzw. es fällt eher runter.

Da kann man Prinzipien wie Clean Code & Co noch so hoch halten und sich noch so sehr als Team drauf einschwören es nicht zu vergessen - es wird passieren. Und es fällt garantiert erst dann auf, wenn man es dann auf einmal doch braucht.

Anders sieht es aus, wenn die toString Methode regelmäßig genutzt und gebraucht wird - dann ist es deutlich einfacher dran zu denken und es fühlt sich für Entwickler weniger wie eine lästige Pflicht an.

Für mich ist wichtig: Clean Code macht man nicht, weil irgendwer anders das für eine gute Idee hält und hält sich dann sklavisch an alles, sondern Clean Code & Co macht man, weil man den Sinn einsieht und versteht, was es einem bringt. Und wenn bestimmte Teilaspekte für das konkrete Projekt mehr Aufwand als Nutzen bringen, dann lässt man es und zwingt es nicht sklavisch drüber. (Nichtsdestotrotz sollte man gute Gründe haben, von bewährten Prinzipien abzuweichen - ein einfaches "Ich hab keinen Bock drauf, ich find das doof" reicht nicht).

Daher stimme ich JustNobody zu - sowas wie Lombok ist da im Zweifelsfall die bessere Wahl (Das hatte ich nicht auf dem Schirm, obwohl mal für ein Projekt erst mal rausfinden musste, wie ich das in Eclipse einbinde)
 
* Bei Dingen, wo der Nutzen schwerer vermittelbar ist (was bei einer toString Mehode der Fall ist, wenn aufgrund der Anwendung das quasi nie relevant ist, weil nie Objekte mittels toString ausgegeben werden) denken die Leute automatisch weniger dran, bzw. es fällt eher runter.

Anders sieht es aus, wenn die toString Methode regelmäßig genutzt und gebraucht wird - dann ist es deutlich einfacher dran zu denken und es fühlt sich für Entwickler weniger wie eine lästige Pflicht an.

Wenn die toString Methode nicht gebraucht wird: warum wird diese dann geschrieben? Und wie rechtfertigt Ihr dies im Bereich Clean Code? Wenn die Anforderung nicht da ist, dann macht es da durchaus Sinn, die auch einfach weg zu lassen. Und dann KISS beachten - warum etwas komplizieren, wenn es ganz trivial geht: @ToString mit einem import reicht ja schon 🙂

Daher stimme ich JustNobody zu - sowas wie Lombok ist da im Zweifelsfall die bessere Wahl (Das hatte ich nicht auf dem Schirm, obwohl mal für ein Projekt erst mal rausfinden musste, wie ich das in Eclipse einbinde)

Das ist relativ trivial. Es gibt ein Addon, welches einfach über die lobok jar Aufgerufen werden kann. Lombok ist da ehh sehr schön. Wenn man das verwendet und die Verwendung gefällt einem nicht, dann kann man die generierte Version der Source Datei nehmen. Auf Wunsch wandelt lombok einem auch alle Files direkt um.

Das ist etwas, das mir sehr gut gefällt, denn wenn ich morgen kein lombok mehr nutzen will, dann ist es eine Sache von wenigen Minuten es komplett aus dem Projekt zu entfernen.
 

Zurück
Oben