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
| Метод | Призначення | Приклад використання |
|---|---|---|
|
Безпечне порівняння двох об’єктів з урахуванням null | |
|
Лаконічне обчислення хеш-коду за кількома полями | |
|
Порівняння через компаратор, безпечно для null | |
|
Перевірка на null, викидає NullPointerException | |
|
Перевірка на null/не null (зручно у Stream API) | |
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.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ