Java Tutorial #35

Objekte in Java vergleichen

2019-02-20 | credit: ©foxyburrow

Thema in Kurzform

Wann sind zwei Objekte "gleich"? Diese (scheinbar banale) Frage sorgt immer wieder für Verwirrung. Der Schlüssel zum Verständnis liegt im Unterschied zwischen dem Vergleichsoperator == und der von Object geerbten Methode equals(). Sie werden sehen: Am Ende hinkt kein Vergleich mehr.

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 dasselbeObjekt 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 Hash Codes der Objekte. Der Hash Code 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:

Infografik Java hashCode() Objekte vergleichen

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! 😎

Werbung

Java lernen

Werde zum Java Profi!

PHP Lernen

Lerne serverbasierte Programmierung

JavaScript lernen

Skille dein Webcoding

FALCONBYTE.NET

Handmade with 🖤️

© 2018-2023 Stefan E. Heller

Impressum | Datenschutz | Changelog

Falconbyte Youtube Falconbyte GitHub facebook programmieren lernen twitter programmieren lernen discord programmieren lernen