Falconbyte unterstüzen
Betrieb und Pflege von Falconbyte brauchen viel Zeit und Geld. Um dir auch weiterhin hochwertigen Content anbieten zu können, kannst du uns sehr gerne mit einem kleinen "Trinkgeld" unterstützen.
Thema in Kurzform
- Der Vergleichsoperator == testet, ob zwei Referenzvariablen auf dasselbe Objekt zeigen.
- Um Objekte nach ihrem Inhalt hin zu vergleichen, wird die Methode equals() eingesetzt. Diese muss aber noch entsprechend überschrieben werden.
- Identität von Referenzen
- Die Methode equals()
- Strings vergleichen
- Übungen
Inhaltsverzeichnis
Identität von Referenzen
Der Vergleichsoperator == ist uns seit Langem im Umgang mit primitiven Datentypen bekannt. Wir können damit zwei primitive Werte auf Gleichheit testen und erhalten als Ergebnis einen booleschen Wert (true oder false) zurück.
Und wie sieht das bei Objekten aus? Im Prinzip ist es genauso. Hier die Regel:
- Der Vergleichsoperator == testet bei Referenzvariablen, ob sie auf dasselbe Objekt verweisen.
Zwei Referenzen, die auf ein identisches Objekt auf dem Heap zeigen, sind also gleich. Hier ein Beispiel:
Point one = new Point(5,5);
Point two = new Point(5,5);
Point three = one;
System.out.println(one == two); // false
System.out.println(one == three); // true
Die beiden Point-Objekte, auf die die Referenzen one und two verweisen, werden zwar mit denselben Parameterwerten instanziiert. Das spielt aber keine Rolle, da es sich um zwei unterschiedliche Objekte handelt. Deshalb ergibt der Test one == two false.
Der Referenzvariablen three wird das Objekt von one zugewiesen. Damit verweisen one und three auf ein und dasselbe Objekt. Somit ist one == three true.
Der Vergleich erfolgt aufgrund des HashCodes der Objekte. Der HashCode ist eine unverwechselbare Nummer, die jedem Objekt eindeutig zugewiesen wird. Mit der von der Klasse Object geerbten Methode hashCode() kann man die Identität der einzelnen Objekte beweisen (one.hashCode() und three.hashCode() liefern denselben Wert):
System.out.println(one.hashCode()); // 621009875
System.out.println(two.hashCode()); // 1265094477
System.out.println(three.hashCode()); // 621009875
Grafisch anschaulich lässt sich unsere Konstellation so darstellen:
Die Methode equals()
Mit == können wir nun also die Identität der Objekte prüfen. Wenn wir dagegen Objekte miteinander vergleichen wollen, müssen wir einen anderen Weg gehen:
- Um den Zustand bzw. Inhalt einzelner Objekte miteinander zu vergleichen, verwenden wir die Methode equals(), die wir aber erst noch durch Überschreiben anpassen müssen.
In der überschriebenen equals()-Methode legen wir dann fest, wie genau die Objekte einer Klasse verglichen werden soll. Das heißt: Wir müssen selbst festlegen, wann zwei Objekte inhaltlich gleich sind.
Für unsere Beispielklasse von oben (Point) heißt das sinnvollerweise: Point-Objekte sind dann inhaltlich gleich, wenn die Instanzvariablen dieselben Werte haben.
Demnach sieht die überschriebene equals()-Methode so aus:
@Override
public boolean equals(Object obj){
if(obj instanceof Point){
Point p = (Point) obj;
return (x == p.x) && (y == p.y);
}
else{
return false;
}
}
Das in den Parameter übergebene Objekt obj wird mit dem Objekt verglichen, auf dem die Methode aufgerufen wird (in unserem Fall sind die beiden Point-Objekte one und two inhaltlich gleich):
System.out.println(one.equals(two)); // true
Besprechen wir noch kurz die Implementierung der überschriebenen equals()-Methode:
Da der Parameter von equals() vom Typ Object sein muss, können wir praktisch Objekte jeden Typs in die Methode reinschicken. Es sollen aber logischerweise nur Objekte vom Typ Point verglichen werden. Deshalb prüfen wir in der Methode als erstes mit dem instanceof-Operator, ob obj eine Instanz der Klasse Point ist.
Ist das Ergenis der Typ-Prüfung false, wird die Methode sogleich mit der Rückgabe von false beendet.
Bevor der Vergleich starten kann, muss obj noch zum Referenztyp Point gecastet werden (aktuell ist die Referenzvariable ja noch Object).
Jetzt erst werden die Instanzvariablen der beiden Objekte verglichen. Wenn die Werte der Instanzvariablen x und y in beiden Objekten diesselben sind, gelten die Objekte als inhaltlich gleich. Die Methode wird mit der Rückgabe von true beendet. Andernfalls endet die Methode mit false (die Objekte sind dann inhaltlich nicht gleich).
Hinterfragt: Warum equals() überschreiben?
Eine gute Frage! Warum der ganze Aufwand? Warum müssen wir für einen Vergleich von Objekten equals() überhaupt überschreiben?
Wenn wir uns die in Object implementierte "Originalmethode" mal ansehen, sehen wir schnell, warum das Überschreiben notwendig ist:
public boolean equals(Object obj) {
return (this == obj);
}
Standardmäßig vergleicht equals() also lediglich die Referenzen und nicht die Objekte. Ein Vergleich von Objekten einer Klasse ist etwas, dass der Compiler unmöglich automatisch kann. Woher soll er wissen, auf welche Weise er die Struktur bzw. den Inhalt unserer Objekte vergleichen soll? Wir müssen es ihm also mitteilen, indem wir die equals()-Methode auf sinnvolle Art überschreiben.
Strings vergleichen
Beim Lernen von Java begegnet man wohl sehr zeitnah primitiven Datentypen und Strings gleichermaßen. Das verleitet viele dazu, Strings wie primitive Datentypen mit dem Operator == zu vergleichen:
String x = "Hello Java!";
String y = "Hello Java!";
System.out.println(x == y); //true
Tatsächlich wird in diesem Beispiel das Ergebnis true zurückgeliefert. Doch wir sehen bereits im nächsten Beispiel, dass Strings nicht mit == vergleichbar sind:
String x = "Hello Java!";
String y = "Hello ".concat("Java!"); // füge "Java!" hinzu
System.out.println(x == y); //false
Obwohl beide Strings "Hello Java!" lauten, ist das Ergebnis false. Was ist die Erklärung für dieses Phänomen?
Java legt eine String-Literale nur einmal im Speicher ab. Im ersten Beispiel zeigen die beiden Variablen x und y auf dasselbe Objekt im Speicher. Deshalb ist das Ergebnis mit == true.
Im zweiten Beispiel liefert x == y false, obwohl beide Variablen dennselben String haben ("Hallo Java!"). Der Grund ist, dass wir hier zwei unterschiedliche String-Objekte haben. Denn der String von y wird durch den Methodenaufruf erst zur Laufzeit erstellt. Da x und y beim Compilieren nicht gleich sind, wird ein neues String-Objekt erzeugt.
Deshalb gilt für den String-Vergleich absolut zwingend folgendes Gesetz:
- Verwenden Sie für den String-Vergleich niemals ==, sondern immer equals().
Praktisch dabei ist, dass wir equals() nicht überschreiben müssen, da die Klasse String dies bereits für uns erledigt hat. Wir können also gleich loslegen:
String x = "Hello Java!";
String y = "Hello ".concat("Java!");
System.out.println(x.equals(y)); // true
So sieht die Sache gut aus! 😎
Übungen
einfach
Sehen Sie sich beide Codes an: Was wird auf der Konsole geprinted?
public class Raven {
private String name;
public Raven(String name){
this.name = name;
}
}
public class Engage {
public static void main(String[] args){
Raven r1 = new Raven("Odin");
Raven r2 = new Raven("Odin");
Raven r3 = r1;
System.out.println(r1 == r2);
System.out.println(r1 == r3);
System.out.println(r1.equals(r2));
}
}
mittel
Überschreiben Sie in der Klasse Raven die equals()-Methode sinnvoll, sodass folgender Code true printed:
public static void main(String[] args){
Raven r1 = new Raven("Odin");
Raven r2 = new Raven("Odin");
System.out.println(r1.equals(r2)); //true
}
schwer
Was ist das Ergebnis des folgenden Codes ?
String s1 = "Java";
String s2 = "Ja".concat("va");
String s3 = s1;
if(s1 == s3){
System.out.print(1);
}
if(s1 == s2){
System.out.print(2);
}
if(s1.equals(s2)){
System.out.print(3);
}
A. 1
B. 12
C. 123
D. 13
E. 23
F. 3
G. nichts