JavaRush /Курси /JAVA 25 SELF /Клас Objects: методи equals, hashCode, hash

Клас Objects: методи equals, hashCode, hash

JAVA 25 SELF
Рівень 29 , Лекція 1
Відкрита

1. Знайомство з класом Objects

Переходимо до теми! Якщо ви втомилися вручну перевіряти значення на null і щоразу обчислювати хеш для кожного поля, вам допоможе утилітний клас java.util.Objects. Його завдання — зробити роботу з об’єктами простішою, лаконічнішою та безпечнішою.

Це справді «швейцарський ніж»: клас уміє безпечно порівнювати об’єкти на рівність (без ризику отримати NullPointerException), зручно обчислювати хеш-коди, порівнювати через компаратор і перевіряти аргументи на null.

Objects.equals: безпечне порівняння з урахуванням null

Якщо написати просто a.equals(b), а a виявиться null, ви отримаєте NullPointerException. Ручні перевірки громіздкі. Objects.equals(a, b) зробить усе за вас:

  • Якщо обидва дорівнюють null — повертає true.
  • Якщо рівно один дорівнює null — повертає false.
  • Якщо обидва не null — викликає звичайний 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

Чому це зручно? Код коротший, чистіший і захищений від випадкових NPE.

2. Objects.hash і hashCode: лаконічне обчислення хешу

Перевизначаючи hashCode разом із equals, легко помилитися, особливо коли полів багато. Ручний код часто виглядає громіздко та крихко:

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

Метод Objects.hash розв’язує проблему — коротко, безпечно та з підтримкою null:

import java.util.Objects;

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

    // ... конструктор, гетери тощо

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

Важливий момент: Objects.hash використовує varargs і створює масив — у рідкісних високонавантажених місцях ручний hashCode може бути швидшим. Для більшості застосунків різниця несуттєва.

3. Objects.compare: делегуємо порівняння компаратору

Іноді потрібно порівняти два об’єкти через заздалегідь підготовлений Comparator. Замість прямого виклику comparator.compare(a, b) можна використати:

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

Цей метод:

  • Повертає 0, якщо об’єкти рівні.
  • Вважає null «меншим» за будь-який не-null об’єкт.
  • В інших випадках делегує логіку переданому компаратору.
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("Анна");
        Person b = new Person("Борис");
        Comparator<Person> byName = Comparator.comparing(Person::getName);

        System.out.println(Objects.compare(a, b, byName)); // <0, оскільки "Анна" < "Борис"
        System.out.println(Objects.compare(a, null, byName)); // >0, оскільки a != null
        System.out.println(Objects.compare(null, b, byName)); // <0, оскільки null < b
        System.out.println(Objects.compare(null, null, byName)); // 0
    }
}

4. Objects.requireNonNull: запобіжник від «невидимих» помилок

Якщо метод має приймати лише не-null значення, перевіряйте це відразу. Objects.requireNonNull кине NullPointerException із вашим повідомленням:

public void setName(String name) {
    this.name = Objects.requireNonNull(name, "Імʼя не може бути null");
}

5. Приклад: коректна реалізація equals, hashCode і compareTo з 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, "Імʼя не може бути 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; // Порівняння за посиланням
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        // Безпечне порівняння з null
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age); // Лаконічно та безпечно
    }

    @Override
    public int compareTo(Person other) {
        // Спочатку порівнюємо за імʼям, потім за віком
        int cmp = name.compareTo(other.name);
        if (cmp != 0) return cmp;
        return Integer.compare(age, other.age);
    }
}

Тепер можна зберігати об’єкти в HashSet, використовувати їх як ключі в HashMap, перевіряти на рівність і сортувати списки (наприклад, через Collections.sort).

6. Застосування в реальних завданнях: скорочення коду та зменшення кількості помилок

Приклад: список користувачів у застосунку

Завдяки коректній парі equals/hashCode пошук у колекціях працює передбачувано:

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

List<Person> users = new ArrayList<>();
users.add(new Person("Анна", 25));
users.add(new Person("Борис", 30));

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

Приклад: робота з nullable-полями

Якщо в класі є поля, які можуть бути null (наприклад, по батькові), використовуйте Objects.equals і Objects.hash:

import java.util.Objects;

public class User {
    private String firstName;
    private String middleName; // Може бути 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. Таблиця: основні методи класу Objects

Метод Призначення Приклад використання
Objects.equals(a, b)
Безпечне порівняння двох об’єктів з урахуванням null
Objects.equals(a, b)
Objects.hash(a, b, ...)
Лаконічне обчислення хеш-коду за кількома полями
Objects.hash(name, age)
Objects.compare(a, b, comparator)
Порівняння через компаратор, безпечно для null
Objects.compare(p1, p2, byNameComparator)
Objects.requireNonNull(obj[, msg])
Перевірка на null, викидає NullPointerException
Objects.requireNonNull(name, "Імʼя не може бути null")
Objects.isNull(obj) / Objects.nonNull(obj)
Перевірка на null/не null (зручно у Stream API)
list.stream().filter(Objects::nonNull)

8. Типові помилки під час використання методів класу Objects

Помилка № 1: забули використати Objects.equals для nullable-полів. Якщо порівнювати поля напряму через equals, можна отримати NullPointerException. Використовуйте Objects.equals(middleName, other.middleName).

Помилка № 2: не всі поля враховані в hashCode. Поля, що беруть участь у equals, мають брати участь і в hashCode, інакше поведінка HashSet/HashMap стане непередбачуваною.

Помилка № 3: ручний hashCode з помилкою. Дотримуватися коефіцієнтів і перевірок на null непросто. Objects.hash робить це за вас; використовуйте його, якщо немає жорстких вимог до продуктивності.

Помилка № 4: не використовуєте Objects.requireNonNull там, де це контракт класу. Якщо поле не допускає null, перевіряйте в конструкторі/сетері — помилка виявиться одразу, а не в глибині стека викликів.

Помилка № 5: використовуєте Objects.hash для масивів. Для масивів потрібен Arrays.hashCode, а для вкладених масивів — Arrays.deepHashCode; аналогічно для порівняння вмісту існують Arrays.equals/Arrays.deepEquals.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ