Was hat es mit String-Objekten in Java auf sich und wie kann man sie vergleichen?

Strings können in Java auf zwei verschiedene Weisen erzeugt werden und werden zudem teilweise intern durch einen Literal Pool gespeichert.

Ein String kann durch Zuweisung des Literals zu einer Variablen direkt oder aber, wie bei anderen Objekten auch, durch Aufruf des Konstruktors mit new gebildet werden. Ein Unterschied zwischen beiden Versionen ist zunächst nicht zu erkennen:

String s1 = "Hallo";
String s2 = new String("Welt!");

Führt man jedoch Vergleiche mit lexikalisch identischen String-Objekten durch, so zeigen sich eigenartige Ergebnisse:

String sl1 = "Hallo";
String sl2 = "Hallo";
String sk1 = new String("Hallo");
String sk2 = new String("Hallo");
System.out.println(sl1 == sl2);			// true
System.out.println(sk1 == sk2);			// false
System.out.println(sl1 == sk1);			// false
System.out.println(sl1.equals(sk1));		// true

Der Vergleichsoperator ' == ' dient beim Objektvergleich der Überprüfung der Identität der Objekte selbst, also ihrem Speicherort, nicht einer lexikalischen Übereinstimmung. Es werden somit Referenzen überprüft, ob sie auf das selbe Objekt zeigen. Möchte man dagegen die lexikalische Gleichheit zweier Strings prüfen, so muss dies mit der Methode equals() erfolgen. Sie ist in der Klasse Object deklariert und wird in der Klasse String überschrieben. Dort werden die beiden zu vergleichenden Strings als Arrays primitiver char -Typen behandelt. Diese werden durchlaufen und die einzelnen char dann auf Übereinstimmung ihrer numerischen Unicode-Werte hin überprüft. Im letzten Beispiel sind alle char -Werte identisch und die Methode gibt true zurück.

Der Literal Pool

Wie kommt es jedoch zur Übereinstimmung der beiden Variablen sl1 und sl2 wo es sich doch auf den ersten Blick um zwei verschiedene Objekte handelt? Die Lösung liefert der Literal Pool, ein in der Klasse String angelegter Speicher, in dem zur Laufzeit je ein Exemplar bereits erzeugter, lexikalisch identischer Strings vorgehalten wird, um Speicher und Performance zu sparen. Er ist zunächst leer. Wird ein String neu erzeugt, so wird zunächst in diesem Pool nachgesehen, ob ein identischer String dort bereits eingetragen ist. Ist dies der Fall, wird lediglich eine Referenz auf den dort registrierten erzeugt, ansonsten wird er dem Pool neu hinzugefügt. Im Beispiel zeigen somit sl1 und sl2 auf das selbe Objekt, von dem im Literal Pool eine Referenz gespeichert ist.

Werden jedoch, wie bei den beiden Variablen sk1 und sk2 String-Objekte mit new erzeugt, so geschieht dies unabhängig vom Pool. Beide Objekte sind somit eigenständige Instanzen mit individuellen Speicherorten, deren Vergleich mit dem Vergleichsoperator false ergeben muss. Man erkennt hier, dass, wenn möglich, neue Strings somit ohne Verwendung des new-Operators erzeugt werden sollten.

Es gibt jedoch eine Möglichkeit, auch ein mit new erzeugtes String-Objekt auf ein lexikalisch identisches, im Pool eingetragenes Objekt zugreifen zu lassen. Die String-Methode intern() bietet diese Möglichkeit. Das folgende Beispiel verdeutlicht dies:

String s1 = "Hallo";
String s2 = new String("Hallo");
String s3 = s2.intern();
System.out.println(s1 == s3);			// true
System.out.println(s2 == s3);			// false