Hallo zusammen,
heute tauchen wir in ein Java-Thema ein, das bei vielen Entwicklern, besonders am Anfang, für Kopfzerbrechen sorgt: Der
Vergleich von Strings mit dem ==
-Operator. Ihr habt sicherlich schon mal gehört, dass man Strings immer mit
.equals()
vergleichen sollte. Aber warum funktionieren dann die folgenden Tests, wenn wir doch ==
verwenden?
String a = "127";
String b = "127";
String c = "128";
String d = "128";
boolean comp = a == b;
boolean comp2 = a == c;
boolean comp3 = b == c;
boolean comp4 = c == d;
assertTrue(comp);
assertFalse(comp2);
assertFalse(comp3);
assertTrue(comp4);
Wenn ihr das in eurem Code ausprobiert, werdet ihr feststellen, dass alle assertTrue
- und assertFalse
-Statements
erfolgreich sind. Aber wie kann das sein? Lasst uns das Mysterium lüften.
String Literale und der String Pool
Das Geheimnis liegt im String Pool (auch bekannt als String Intern Pool oder String Constant Pool). Dieser Pool ist ein spezieller Bereich im Heap-Speicher von Java, in dem String-Literale gespeichert werden.
Wenn die Java Virtual Machine (JVM) auf ein String-Literal (also einen String, der direkt im Code in doppelten
Anführungszeichen steht, wie "127"
oder "Hello"
) trifft, passiert Folgendes:
- Prüfung im String Pool: Die JVM prüft zuerst, ob ein String mit dem gleichen Wert bereits im String Pool existiert.
- Existiert er?
- Ja: Wenn der String bereits existiert, wird einfach eine Referenz auf diesen bereits bestehenden String im Pool zurückgegeben. Es wird kein neuer String erzeugt.
- Nein: Wenn der String noch nicht im Pool ist, wird ein neuer String im Pool erstellt und eine Referenz darauf zurückgegeben.
Die Magie hinter den Tests
Schauen wir uns eure Beispiele im Detail an:
String a = "127";
String b = "127";
Hier werden beide String-Literale auf den Wert "127"
gesetzt. Da "127"
ein String-Literal ist, wird die JVM zuerst
prüfen, ob "127"
bereits im String Pool ist. Da dies das erste Mal für diesen Wert ist, wird "127"
im Pool angelegt,
und sowohl a
als auch b
erhalten eine Referenz auf dasselbe String-Objekt im String Pool. Daher ist a == b
*
*true
**.
String c = "128";
String d = "128";
Hier geschieht genau das Gleiche wie bei "127"
. Auch "128"
ist ein String-Literal. Es wird im String Pool angelegt (
sofern nicht schon vorhanden), und c
und d
verweisen auf dasselbe Objekt im Pool. Deshalb ist c == d
ebenfalls
true
.
Der Haken: Warum .equals()
Standard sein sollte
Jetzt kommt der interessante Teil, der die Verwirrung oft erst richtig entstehen lässt:
boolean comp2 = a == c; // a="127", c="128"
boolean comp3 = b == c; // b="127", c="128"
In diesen Fällen vergleichen wir Referenzen auf unterschiedliche String-Literale. "127"
und "128"
sind natürlich
zwei verschiedene Werte. Die JVM legt sie als separate Objekte im String Pool an. a
und b
verweisen auf das Objekt
für "127"
, während c
und d
auf das Objekt für "128"
verweisen. Da a
(bzw. b
) und c
auf verschiedene
Speicheradressen zeigen, ist der ==
-Vergleich (der die Referenzen vergleicht) hier korrekt false
.
Wann ==
nicht funktioniert
Der ==
-Operator funktioniert nur dann zuverlässig für String-Vergleiche, wenn beide Strings als Literale im
Code definiert sind und die JVM das String Interning anwendet. Sobald ein String auf andere Weise erzeugt wird, z.B.
durch:
new String("Wert")
: Hier wird explizit ein neues Objekt im Heap erzeugt, selbst wenn “Wert” bereits im String Pool ist. Die Referenz ist dann unterschiedlich.- String-Operationen:
String result = str1 + str2;
oderString result = str.substring(0, 3);
erzeugen ebenfalls * neue String-Objekte* im Heap.
In diesen Fällen würden Vergleiche mit ==
fast immer false
zurückgeben, auch wenn die String-Inhalte identisch
wären.
String s1 = "Hallo";
String s2 = "Hallo";
String s3 = new String("Hallo");
System.out.println(s1 == s2); // true (beide Literale, gleiches Objekt im Pool)
System.out.println(s1 == s3); // false (s3 ist ein neues Objekt im Heap)
System.out.println(s1.equals(s3)); // true (Inhalte sind gleich)
Fazit
Die Beispiele zeigen, dass ==
für String-Literale funktionieren kann, weil die JVM durch den String Pool eine
Optimierung vornimmt. Aber verlasst euch niemals darauf!
Als Best Practice gilt: Verwendet IMMER die .equals()
-Methode zum Vergleich von String-Inhalten. Sie vergleicht
die tatsächlichen Zeichensequenzen der Strings und ist damit die sichere und korrekte Methode für den
String-Inhaltsvergleich in Java, unabhängig davon, wie die Strings erzeugt wurden.
Habt ihr noch Fragen dazu oder seid ihr auch schon mal über dieses Verhalten gestolpert? Lasst es mich wissen!