CodeGym /Kursy /JAVA 25 SELF /Klasa Objects: metody equals, hashCode, hash

Klasa Objects: metody equals, hashCode, hash

JAVA 25 SELF
Poziom 29, Lekcja 1
Dostępny

1. Zapoznanie z klasą Objects

Przechodzimy do tematu! Jeśli masz dość ręcznego sprawdzania wartości pod kątem null i pisania obliczania hashy dla każdego pola, z pomocą przyjdzie klasa narzędziowa java.util.Objects. Jej zadanie — uprościć pracę z obiektami, uczynić ją zwięzłą i bezpieczną.

To naprawdę „szwajcarski scyzoryk”: klasa potrafi bezpiecznie porównywać obiekty pod względem równości (bez ryzyka NullPointerException), wygodnie obliczać kody skrótu (hash), porównywać przez komparator oraz sprawdzać argumenty pod kątem null.

Objects.equals: bezpieczne porównanie z uwzględnieniem null

Jeśli napiszesz po prostu a.equals(b), a a okaże się null, poleci NullPointerException. Ręczne sprawdzanie jest rozwlekłe. Objects.equals(a, b) robi to za ciebie:

  • Jeśli oba są null — zwraca true.
  • Jeśli dokładnie jeden jest null — zwraca false.
  • Jeśli oba nie są null — wywołuje zwykłe equals.
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

Dlaczego to wygodne? Kod jest krótszy, czytelniejszy i odporny na przypadkowe NPE.

2. Objects.hash i hashCode: zwięzłe obliczanie hashy

Nadpisując hashCode wraz z equals, łatwo o błąd, zwłaszcza gdy pól jest dużo. Ręczny kod często bywa rozwlekły i kruchy:

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

Metoda Objects.hash rozwiązuje problem — krótko, bezpiecznie i z obsługą null:

import java.util.Objects;

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

    // ... konstruktor, gettery itp.

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

Ważna uwaga: Objects.hash używa varargs i tworzy tablicę — w rzadkich, wysoko obciążonych miejscach ręcznie napisany hashCode może być szybszy. Dla większości aplikacji różnica jest nieistotna.

3. Objects.compare: delegujemy porównanie komparatorowi

Czasem trzeba porównać dwa obiekty poprzez wcześniej przygotowany Comparator. Zamiast wywoływać wprost comparator.compare(a, b) można użyć:

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

Ta metoda:

  • Zwraca 0, jeśli obiekty są równe.
  • Uznaje null za „mniejsze” od każdego nie-null obiektu.
  • W pozostałych przypadkach deleguje logikę przekazanemu komparatorowi.
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, ponieważ "Anna" < "Boris"
        System.out.println(Objects.compare(a, null, byName)); // >0, bo a != null
        System.out.println(Objects.compare(null, b, byName)); // <0, bo null < b
        System.out.println(Objects.compare(null, null, byName)); // 0
    }
}

4. Objects.requireNonNull: asekuracja przed „niewidzialnymi” błędami

Jeśli metoda powinna przyjmować tylko wartości nie-null, sprawdzaj to od razu. Objects.requireNonNull rzuci NullPointerException z twoją wiadomością:

public void setName(String name) {
    this.name = Objects.requireNonNull(name, "Imię nie może być null");
}

5. Przykład: poprawna implementacja equals, hashCode i compareTo z 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, "Imię nie może być null");
        this.age = age;
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true; // Porównanie przez referencję
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        // Bezpieczne porównanie z null
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age); // Zwięźle i bezpiecznie
    }

    @Override
    public int compareTo(Person other) {
        // Najpierw porównujemy po imieniu, potem po wieku
        int cmp = name.compareTo(other.name);
        if (cmp != 0) return cmp;
        return Integer.compare(age, other.age);
    }
}

Teraz można przechowywać obiekty w HashSet, używać ich jako kluczy w HashMap, porównywać pod względem równości i sortować listy (np. przez Collections.sort).

6. Zastosowanie w realnych zadaniach: mniej kodu i mniej błędów

Przykład: lista użytkowników w aplikacji

Dzięki poprawnej parze equals/hashCode wyszukiwanie w kolekcjach działa przewidywalnie:

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

Przykład: praca z polami nullable

Jeśli klasa ma pola, które mogą być null (np. patronimik), używaj Objects.equals i Objects.hash:

import java.util.Objects;

public class User {
    private String firstName;
    private String middleName; // Może być null
    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. Tabela: główne metody klasy Objects

Metoda Przeznaczenie Przykład użycia
Objects.equals(a, b)
Bezpieczne porównanie dwóch obiektów z uwzględnieniem null
Objects.equals(a, b)
Objects.hash(a, b, ...)
Zwięzłe obliczanie kodu hash na podstawie wielu pól
Objects.hash(name, age)
Objects.compare(a, b, comparator)
Porównanie przez komparator, bezpieczne dla null
Objects.compare(p1, p2, byNameComparator)
Objects.requireNonNull(obj[, msg])
Sprawdzenie na null, rzuca NullPointerException
Objects.requireNonNull(name, "Imię nie może być null")
Objects.isNull(obj) / Objects.nonNull(obj)
Sprawdzenie null/nie null (wygodne w Stream API)
list.stream().filter(Objects::nonNull)

8. Typowe błędy przy używaniu metod klasy Objects

Błąd nr 1: zapomniano użyć Objects.equals dla pól nullable. Jeśli porównujesz pola bezpośrednio przez equals, możesz złapać NullPointerException. Użyj Objects.equals(middleName, other.middleName).

Błąd nr 2: nie wszystkie pola uwzględnione w hashCode. Pola biorące udział w equals powinny brać udział także w hashCode, inaczej zachowanie HashSet/HashMap stanie się nieprzewidywalne.

Błąd nr 3: ręczny hashCode z błędem. Dbanie o współczynniki i sprawdzanie null nie jest proste. Objects.hash robi to za ciebie; używaj go, jeśli nie masz rygorystycznych wymagań wydajnościowych.

Błąd nr 4: nie używasz Objects.requireNonNull tam, gdzie to kontrakt klasy. Jeśli pole nie dopuszcza null, sprawdzaj to w konstruktorze/setterze — błąd ujawni się od razu, a nie w „gąszczu” stosu wywołań.

Błąd nr 5: używasz Objects.hash dla tablic. Dla tablic należy użyć Arrays.hashCode, a dla zagnieżdżonych tablic — Arrays.deepHashCode; analogicznie do porównywania zawartości istnieją Arrays.equals/Arrays.deepEquals.

1
Zadanie
JAVA 25 SELF, poziom 29, lekcja 1
Niedostępne
Tworzenie odcisku cyfrowego dla książek w katalogu 📚
Tworzenie odcisku cyfrowego dla książek w katalogu 📚
1
Zadanie
JAVA 25 SELF, poziom 29, lekcja 1
Niedostępne
Porządkowanie uczestników wydarzenia według imienia 🧑‍🤝‍🧑
Porządkowanie uczestników wydarzenia według imienia 🧑‍🤝‍🧑
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION