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.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ