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