Java Tutorial #26

Objekte vergleichen

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.

Stefan 02.01.2019

Infos zum Artikel

Kategorie Java
Autor Stefan
Datum 02.02.2019

Thema in Kurzform

  • Der Vergleichsoperator == testet, ob zwei Variablen auf dasselbe Objekt zeigen.
  • Um Objekte nach ihrem Inhalt hin zu vergleichen, wird die Methode equals() eingesetzt. Diese muss aber noch entsprechend überschrieben werden.
  • Beim Vergleich von zwei String-Objekten, ist ein Überschreiben von equals() nicht möglich und auch nicht notwendig.

Identität von Referenzen

Der Vergleichsoperator == ist uns seit Langem im Umgang mit primitiven Datentypen bekannt (hier). 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() lieferen 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:

Java HashCode

Die Methode equals()

Mit == können wir nun also die Identität der Objekte prüfen. Wenn wir dagegen Objekte miteinander vergleichen wollen, müssern 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 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 diesselben 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. Dieser Check ist wichtig, da wir andernfalls nicht vergleichen können und das Programm zur Laufzeit crasht.

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 Objekten vergleichen soll? Wir müssen es ihm also mitteilen, indem wir die equals()-Methode auf sinnvolle Art überschreiben.

Strings vergleichen

Beim Lernen von Java lernt man ganz am Anfang wohl sehr zeitnah primitiven Datentypen und Strings kennen. 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));

    }

}
Lösung ein-/ausblenden

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

}
Lösung ein-/ausblenden

schwer

Was ist das Ergebnis folgender 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

Lösung ein-/ausblenden

Konstruktoren bei Vererbung

Trainieren Sie ihre Konstruktoren-Skills

Vererbung einfach erklärt

Lernen Sie die Grundlagen der Vererbung in Java

Objekte vergleichen

Was ist der Unterschied zwischen == und equals()?

FALCONBYTE.NET

Handmade with 🖤️

© 2018, 2019 Stefan E. Heller

Impressum | Datenschutz

facebook programmieren lernen twitter programmieren lernen youtube programmieren lernen