CodeGym /Kurse /JAVA 25 SELF /Klasse Objects: Methoden equals, hashCode, hash

Klasse Objects: Methoden equals, hashCode, hash

JAVA 25 SELF
Level 29 , Lektion 1
Verfügbar

1. Einführung in die Klasse Objects

Los geht’s! Wenn Sie es leid sind, Werte von Hand auf null zu prüfen und für jedes Feld den Hash zu berechnen, hilft der Utility-Typ java.util.Objects. Seine Aufgabe ist es, die Arbeit mit Objekten einfacher, prägnanter und sicherer zu machen.

Er ist tatsächlich ein „Schweizer Taschenmesser“: Die Klasse kann Objekte sicher auf Gleichheit vergleichen (ohne Risiko einer NullPointerException), Hashcodes bequem berechnen, über einen Comparator vergleichen und Argumente auf null prüfen.

Objects.equals: sicheres Vergleichen unter Berücksichtigung von null

Wenn man einfach a.equals(b) schreibt und a ist null, gibt es eine NullPointerException. Manuelle Prüfungen sind umständlich. Objects.equals(a, b) erledigt alles für Sie:

  • Wenn beide null sind – wird true zurückgegeben.
  • Wenn genau eines null ist – wird false zurückgegeben.
  • Wenn beide nicht null sind – wird das normale equals aufgerufen.
import java.util.Objects;

String a = null;
String b = "Java";
System.out.println(Objects.equals(a, b)); // false

String c = null;
System.out.println(Objects.equals(a, c)); // true

String d = "Java";
String e = "Java";
System.out.println(Objects.equals(d, e)); // true

Warum ist das praktisch? Der Code ist kürzer, sauberer und vor zufälligen NPEs geschützt.

2. Objects.hash und hashCode: prägnante Hash-Berechnung

Beim Überschreiben von hashCode zusammen mit equals macht man leicht Fehler, besonders wenn es viele Felder gibt. Manueller Code sieht oft umständlich aus und ist fehleranfällig:

@Override
public int hashCode() {
    int result = 17;
    result = 31 * result + (name != null ? name.hashCode() : 0);
    result = 31 * result + age;
    return result;
}

Die Methode Objects.hash löst das Problem – kurz, sicher und mit Unterstützung für null:

import java.util.Objects;

public class Person {
    private String name;
    private int age;

    // ... Konstruktor, Getter usw.

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

Wichtiger Punkt: Objects.hash verwendet Varargs und erstellt ein Array – in seltenen leistungskritischen Abschnitten kann ein manueller hashCode schneller sein. Für die meisten Anwendungen ist der Unterschied unerheblich.

3. Objects.compare: den Vergleich an den Comparator delegieren

Manchmal muss man zwei Objekte über einen vorbereiteten Comparator vergleichen. Statt comparator.compare(a, b) direkt aufzurufen, kann man verwenden:

int result = Objects.compare(a, b, comparator);

Diese Methode:

  • Gibt 0 zurück, wenn die Objekte gleich sind.
  • Betrachtet null als „kleiner“ als jedes nicht-null-Objekt.
  • In allen anderen Fällen delegiert sie die Logik an den übergebenen Comparator.
import java.util.Comparator;
import java.util.Objects;

class Person {
    private String name;
    Person(String name) { 
        this.name = name; 
    }
    public String getName() { 
        return name; 
    }
}

public class Main {
    public static void main(String[] args) {
        Person a = new Person("Anna");
        Person b = new Person("Boris");
        Comparator<Person> byName = Comparator.comparing(Person::getName);

        System.out.println(Objects.compare(a, b, byName)); // <0, da "Anna" < "Boris"
        System.out.println(Objects.compare(a, null, byName)); // >0, da a != null ist
        System.out.println(Objects.compare(null, b, byName)); // <0, da null < b
        System.out.println(Objects.compare(null, null, byName)); // 0
    }
}

4. Objects.requireNonNull: Schutz vor „unsichtbaren“ Fehlern

Wenn eine Methode nur nicht-null-Werte akzeptieren darf, prüfen Sie das sofort. Objects.requireNonNull wirft eine NullPointerException mit Ihrer Nachricht:

public void setName(String name) {
    this.name = Objects.requireNonNull(name, "Name darf nicht null sein");
}

5. Beispiel: korrekte Implementierung von equals, hashCode und compareTo mit Objects

import java.util.Objects;

public class Person implements Comparable<Person> {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = Objects.requireNonNull(name, "Name darf nicht null sein");
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true; // Vergleich per Referenz
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        // Sicherer Vergleich mit null
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age); // Kompakt und sicher
    }

    @Override
    public int compareTo(Person other) {
        // Zuerst nach Namen, dann nach Alter vergleichen
        int cmp = name.compareTo(other.name);
        if (cmp != 0) return cmp;
        return Integer.compare(age, other.age);
    }
}

Nun kann man Objekte in einem HashSet speichern, sie als Schlüssel in einer HashMap verwenden, auf Gleichheit vergleichen und Listen sortieren (zum Beispiel über Collections.sort).

6. Einsatz in realen Aufgaben: weniger Code und weniger Fehler

Beispiel: Benutzerliste in einer Anwendung

Dank eines korrekten Paars aus equals/hashCode funktioniert die Suche in Collections vorhersagbar:

import java.util.ArrayList;
import java.util.List;

List<Person> users = new ArrayList<>();
users.add(new Person("Anna", 25));
users.add(new Person("Boris", 30));

Person search = new Person("Anna", 25);
System.out.println(users.contains(search)); // true

Beispiel: Arbeit mit Feldern, die null sein können

Wenn eine Klasse Felder hat, die null sein können (zum Beispiel der zweite Vorname), verwenden Sie Objects.equals und Objects.hash:

import java.util.Objects;

public class User {
    private String firstName;
    private String middleName; // Kann null sein
    private String lastName;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(firstName, user.firstName)
            && Objects.equals(middleName, user.middleName)
            && Objects.equals(lastName, user.lastName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(firstName, middleName, lastName);
    }
}

7. Tabelle: zentrale Methoden der Klasse Objects

Methode Zweck Beispiel
Objects.equals(a, b)
Sicherer Vergleich zweier Objekte unter Berücksichtigung von null
Objects.equals(a, b)
Objects.hash(a, b, ...)
Prägnante Hashcode-Berechnung über mehrere Felder
Objects.hash(name, age)
Objects.compare(a, b, comparator)
Vergleich über einen Comparator, null-sicher
Objects.compare(p1, p2, byNameComparator)
Objects.requireNonNull(obj[, msg])
Null-Prüfung, wirft NullPointerException
Objects.requireNonNull(name, "Name darf nicht null sein")
Objects.isNull(obj) / Objects.nonNull(obj)
Prüfung auf null/nicht null (praktisch im Stream-API)
list.stream().filter(Objects::nonNull)

8. Typische Fehler bei der Verwendung der Methoden der Klasse Objects

Fehler Nr. 1: vergessen, Objects.equals für nullbare Felder zu verwenden. Wenn man Felder direkt über equals vergleicht, kann eine NullPointerException auftreten. Verwenden Sie Objects.equals(middleName, other.middleName).

Fehler Nr. 2: nicht alle Felder sind in hashCode berücksichtigt. Felder, die in equals berücksichtigt werden, müssen auch in hashCode einfließen, sonst wird das Verhalten von HashSet/HashMap unvorhersehbar.

Fehler Nr. 3: fehlerhafter manueller hashCode. Faktoren und Null-Prüfungen korrekt einzuhalten, ist nicht trivial. Objects.hash übernimmt das für Sie; verwenden Sie es, sofern es keine strikten Performance-Anforderungen gibt.

Fehler Nr. 4: Objects.requireNonNull nicht dort einsetzen, wo es der Klassenvertrag verlangt. Wenn ein Feld kein null zulässt, prüfen Sie das im Konstruktor/Setter – der Fehler zeigt sich sofort und nicht irgendwo tief im Aufruf-Stack.

Fehler Nr. 5: Objects.hash für Arrays verwenden. Für Arrays ist Arrays.hashCode geeignet, für verschachtelte Arrays Arrays.deepHashCode; analog zum Vergleich des Inhalts gibt es Arrays.equals/Arrays.deepEquals.

Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION