Wenn wir Objekte auf Gleichheit vergleichen möchten, benötigen wir natürlich die
equals()-Methode. Die implementieren wir in der jeweiligen Klasse und programmieren sie so, dass sie zwei Objekte miteinander vergleichen kann. Schnappen wir uns mal eine einfache Personen-Klasse:
public class Person {
private String firstName;
private String lastName;
}
Jetzt führen wir einmal die folgenden Vergleiche durch. Sie werden alle wunderbar funktionieren.
Person person1 = new Person("Phil", "Harmonie");
Person person2 = new Person("Elle", "Fant");
Person person3 = new Person("Phil", "Harmonie");
System.out.println("Person1 equals Person2: " + person1.equals(person2));
System.out.println("Person2 equals Person3: " + person2.equals(person3));
System.out.println("Person3 equals Person2: " + person3.equals(person3));
System.out.println("Person1 equals null: " + person1.equals(null));
Sie werden einwandfrei funktionieren, unter der Voraussetzung, dass wir daran gedacht haben, in der
equals()-Methode einen
null-Wert abzufragen. Haben wir das vergessen, wird uns der letzte Vergleich mit einer
NullPointerException um die Ohren fliegen.
Daher gibt es die Hilfsmethode
Objects.equals(). Sie erhält zwei Übergabeparameter und tut nichts weiter als deren
equals()-Methode aufzurufen. Sie verspricht dabei null-sicher zu sein, d.h. sie kann mit
null-Werten umgehen. Wir brauchen also die Überprüfung auf
null in die
equals()-Methode nicht mehr einzubauen:
System.out.println("Person1 equals Person2: " + Objects.equals(person1, person2));
System.out.println("Person2 equals Person3: " + Objects.equals(person2, person3));
System.out.println("Person3 equals Person1: " + Objects.equals(person3, person1));
System.out.println("null equals Person1: " + Objects.equals(null, person1));
System.out.println("null equals Person1: " + Objects.equals(person1, null));
System.out.println("Person1 equals null: " + Objects.equals(null, null));
Wenn einer der beiden Parameter
null ist, gibt
Objects.equals() false aus. Sind beide Parameter gleich, wird
true ausgegeben. Wenn beide Parameter
null sind, also der letzte Fall im Beispiel, sind beide Parameter gleich und somit ist die Rückgabe konsequenterweise
true.
Nun ist mir beim Experimentieren ein Problem aufgefallen. Sie müssen unbedingt in
die equals()-Methode einen Test auf die richtige Klasse mit
instanceof einbauen, denn sonst gibt es auf jeden Fall eine
NullPointerException, wenn die zweite Übergabe null ist. Warum das so ist, zeige ich gleich. Erst einmal ein bisschen Code, der zeigt, was ich meine:
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Person)) {
return false;
}
Person other = (Person) obj;
if (firstName == null) {
if (other.firstName != null)
return false;
} else if (!firstName.equals(other.firstName))
return false;
if (lastName == null) {
if (other.lastName != null)
return false;
} else if (!lastName.equals(other.lastName))
return false;
return true;
}
Den Test auf
null habe ich weggelassen. Aber die erste if-Anweisung, die das
instanceof enthält, muss unbedingt vorhanden sein. Das liegt an der - meiner Meinung nach nicht ganz glücklichen Implementierung der Methode
Objects.equals(). In einer guten IDE können wir uns die Implementierung ansehen:
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
Sie sehen, dass eigentlich nur der erste Parameter auf
null überprüft wird. Und dann wird die
equals()-Methode mit dem zweiten Parameter aufgerufen. Ist der zweite Parameter also null und wird dann in
equals() eine Methode aufgerufen wie z.B.
getClass(), kracht es auch hier mit einer
NullPointerException. Es hat ein paar Minuten gedauert, bis ich das mit einer Standardimplementierung der
equals()-Methode von Eclipse herausgefunden habe. An der Implementierung sehen Sie allerdings auch, dass Sie sich ebenfalls den Referenzvergleich mit == schenken können.
Also so ganz hält die Methode ihr Versprechen nicht. Aber ein
instanceof müssen Sie in der
equals()-Methode sowieso verwenden, bevor Sie beide Parameter von Object in den jeweiligen Objekttyp casten. Dann schreiben Sie
instanceof einfach direkt an den Anfang und arbeiten nicht mit der
getClass()-Methode.
Natürlich können Sie Ihre
equals()-Methode nur derart kürzen, wenn Sie und Ihr Team konsequent
Objects.equals() für Objektvergleiche einsetzen.
Das oben beschriebene Problem kann auch noch anders umgangen werden.
Objects bietet auch noch die Methode
deepEquals() an, mit der man auch bspw. Arrays miteinander vergleichen kann. Betrachten wir mal deren Implementierung:
public static boolean deepEquals(Object a, Object b) {
if (a == b)
return true;
else if (a == null || b == null)
return false;
else
return Arrays.deepEquals0(a, b);
}
Und hier ist zu sehen, dass tatsächlich beide Parameter auf
null geprüft werden. Mit dieser Methode wäre man also entsprechend noch sicherer unterwegs bei den folgenden Vergleichen.
System.out.println("null equals Person1: " + Objects.deepEquals(null, person1));
System.out.println("null equals Person1: " + Objects.deepEquals(person1, null));
System.out.println("Person1 equals null: " + Objects.deepEquals(null, null));