Was hat es mit String-Objekten in Java auf sich und wie kann man sie vergleichen?
String-Vergleiche mit '==
'
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 kann dies u.a. 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
Methoden zum String-Vergleich
Strings können auf mehrere Arten lexikalisch verglichen
werden. Die Klasse String
stellt dazu selbst eine
Reihe an Methoden zur Verfügung.
boolean equals(Object anObject)
Wie oben bereits erwähnt, kann equals()
zum lexikalischen Vergleich herangezogen werden. Intern arbeitet
die Methode so, dass der aufrufende String zunächst auf
Identität mit dem als Parameter übergebenen Object
geprüft wird. Bei Gleichheit wird true
zurückgegeben. Ist dies nicht der Fall, so werden beide zu
vergleichende Strings in char
-Arrays gewandelt,
diese durchlaufen und char
für char
gegeneinander geprüft. Bei Ungleichheit eines char
-Paares
wird false
zurückgegeben und die Methode
terminiert.
System.out.println("Foo".equals("Foo")); // true System.out.println("Foo".equals("foo")); // false
boolean equalsIgnoreCase(String str)
Diese Methode ignoriert die Groß-/Kleinschreibung und
bedient sich der Überprüfung mittels regionMatches()
,
die Stringvergleiche von Teilstrings vornehmen kann. Ihr werden
in diesem Fall die vollständigen Strings übergeben und
diese von Anfang bis Ende verglichen. Auch hier werden intern
wieder char[]
verwendet, die durchlaufen und Index
für Index verglichen werden. Bei Ungleichheit oder auch bei
unterschiedlicher Stringlänge wird false
zurückgegeben.
System.out.println("Foo".equalsIgnoreCase("Foo")); // true System.out.println("Foo".equalsIgnoreCase("foo")); // true
int compareTo(String str)
Wie die vorhergehenden basiert auch diese Methode auf dem
Vergleich der char
-Werte beider Strings.
Zurückgegeben wird allerdings ein int
. Dieser
wird folgendermaßen ermittelt: Nach dem Konvertieren der zu
vergleichenden Strings in char[]
wird die
Länge des kürzeren Arrays ermittelt. Sie wird als
Abbruchbedingung einer Schleife verwendet, in der beide Arrays
gleichzeitig durchlaufen und die char
an gleichen
Arraypositionen verglichen werden. Sind sie nicht gleich, so
wird die Differenz der Unicode-Werte zurückgegeben.
Läuft die Schleife bis zum Ende, wird die
Längendifferenz zwischen beiden Strings zurückgegeben.
System.out.println("FooBar".compareTo("Foobar")); // -32 System.out.println('B' - 'b'); // -32
int compareToIgnoreCase(String str)
Die Methode ignoriert Unterschiede in der
Groß-/Kleinschreibung und bedient sich eines etwas anderen
Vorgehens: Sie ruft das statische Feld CASE_INSENSITIVE_ORDER
auf, das ein Comparator
-Objekt der privaten Klasse
CaseInsensitiveComparator
speichert. Die Klasse
deklariert die Methode compare(String s1, String s2)
.
Hier geschieht nun allerdings ähnliches wie in compareTo()
:
Die Strings werden in char
-Arrays gewandelt und
diese durchlaufen und verglichen. Die char
-Werte
werden jedoch vorher durch die Wrapper-Klasse Character
gekapselt und vor dem Vergleich dort hinsichtlich der
Groß-/Kleinschreibung normalisiert. Die Differenz wird dann
zurückgegeben.
System.out.println("FooBar".compareToIgnoreCase("Foobar")); // 0
boolean regionMatches()
Die Methode ist überladen und mit zwei unterschiedlichen
Parameterlisten deklariert.
regionMatches(int toffset,
String other, int ooffset, int len)
regionMatches(boolean
ignoreCase, int toffset, String other, int ooffset, int len)
Bei der zweiten Variante wird als erstes noch ein Parameter
für die Berücksichtigung der
Groß-/Kleinschreibung angegeben. Er muss true
sein, wenn die Groß-/Kleinschreibung ignoriert werden soll.
Intern
arbeiten beide Methoden wieder mit char
-Vergleichen,
wobei der Vergleich zwischen groß und klein geschriebenen char
-Literalen
wiederum durch Character.toUpperCase()
und Character.toLowerCase()
normalisiert wird.
String s1 = "DampfSchiffFahrt", s2 = "schiff"; System.out.println(s1.regionMatches(true, 5, s2, 0, 6)); // true System.out.println(s1.regionMatches(true, 5, s2, 0, 3)); // true System.out.println(s1.regionMatches(true, 7, s2, 2, 2)); // true
Wenn Ihnen javabeginners.de gefällt, freue ich mich über eine Spende an diese gemeinnützigen Organisationen.