Demo zu equals(…), hashCode(), compareTo(…), Collections und Weiteres
Es wird eine einfache Personenklasse zun Demonstrieren der im Titel erwähnten Themen definiert:
1. Basis-Info zur Demo-Klasse Person
public class Person implements Comparable<Person> {
public static final int MIN_BIRTH_YEAR = 1900; // durch 'final' konstant -->
public static final int MAX_BIRTH_YEAR = 2022; //.. MÜSSEN in Deklaration Wert erhalten
public enum Gender {
FEMALE, MALE, OTHER, UNKNOWN;
}
private String lastname;
private String firstname;
private Gender gender;
private int birthYear;
private String email;
// ...
// @Override public int hashCode() { ... }
// @Override public boolean equals(Object obj) { ... }
// @Override public int compareTo(Person o) { ... } // definiert im Interface Comparable<Person>
}
2. Implementation hashCode() und equals(…)
Ein erster Anwendungsfall ist die Annahme, dass die 4 Attribute lastname
, firstname
, gender
und birthYear
Eindeutigkeit einer Person sicherstellen (stimmt nicht - dazu müsste das vollständige Geburtsdatum und der Geburtsort gegeben sein, dann wird im jeweiligen Standesamt dafür gesorgt, dass es keine 2 Franz Meier am selben Tag im selben Ort/Gemeindebezirk gibt).
Dies wird für die Re-Definition (Override) der beiden wichtigen, schon in der Root-Klasse Object definierten Methoden equals(…)
und hashCode()
benötigt.
Unter dieser Annahme und durch Auswahl dieser Attribute im Generator-Dialog kann man in der IDE die folgenden Methoden generieren.
Hier wurden in Eclipse die Checkboxen "Use 'instanceof' to compare types" und "Use Objects.hash and Objects.equals methods (1.7 or higher)" angekreuzt – dies macht die Methoden wesentlich kompakter.
Zusätzlich wurde in equals(…)
händisch das this.
vor die eigenen Instanzvariablen gesetzt, da damit die Symmetrie this
←→ other
besser sichtbar wird, weiters wurden erläuternde Kommentare eingefügt:
@Override
public int hashCode() {
// in der JDK-Methode Objects.hash(...) wird schon die ganze Arbeit erledigt:
return Objects.hash(this.birthYear, this.firstname, this.gender, this.lastname);
}
@Override
public boolean equals(Object obj) {
if (this == obj) // identisches Objekt übergeben (sich selbst)
return true;
if (!(obj instanceof Person)) // <-- falls 'obj' keine Person, kann es nicht gleich sein
return false; // |
Person other = (Person) obj; // obiges 'instanceof' sichert die Gültigkeit des type cast
return this.birthYear == other.birthYear && Objects.equals(this.firstname, other.firstname)
&& this.gender == other.gender && Objects.equals(this.lastname, other.lastname);
}
Bei Annahme, dass die E-Mail-Adresse Eindeutigkeit sichert (typisch für Online-Accounts) ist die Instanzvariable email
als Einziges auszuwählen und es entsteht bei gleicher Vorgehensweise:
@Override
public int hashCode() {
return Objects.hash(this.email);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof Person))
return false;
Person other = (Person) obj;
return Objects.equals(this.email, other.email);
}
3. Implementation compareTo(…)
Die wegen Implementierens des Interface Comparable<T>
zu implementierende Methode public int compareTo(Person o) { … }
erfordert implizit die Festlegung, welche "natürliche" Ordnung man für Personen verwenden möchte.
Hier wählen wir als stärkstes Kriterium lastname
, dann firstname
, dann birthYear
, zuletzt gender
:
@Override
public int compareTo(Person other) {
int cmp;
if ((cmp = this.lastname.compareTo(other.lastname)) != 0) { // bei Gleichheit gilt cmp == 0
return cmp; // das erste Kriterium ergibt Ungleichheit und hat damit Klarheit geschaffen
}
if ((cmp = this.firstname.compareTo(other.firstname)) != 0) {
return cmp;
}
if ((cmp = this.birthYear - other.birthYear) != 0) {
return cmp;
}
if ((cmp = this.gender.compareTo(other.gender)) != 0) {
return cmp;
}
return 0; // alle Kriterien sind gleich, daher ist Gleichheit gegeben --> 0
// return this.email.compareTo(other.email); // falls nur email verwendet würde ...
}
Nun wollen wir einige Collection-Typen ansehen