Что будет, если объект, вызвавший equals, равен null
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
equals() с null: Тонкости и Правила
Этот вопрос проверяет понимание контракта equals() в Java и обработки null значений. Ответ кажется простым, но содержит важные детали.
Краткий Ответ
Если вызвать null.equals(anything), получим NullPointerException. Но это NOT вызов equals() на null объекте — это попытка вызвать метод на null.
Если правильно реализовать equals(), он должен вернуть false при сравнении с null.
Различие: Вызов на null vs Параметр null
Сценарий 1: Вызов метода на null объекте
Object obj = null;
obj.equals("something"); // ❌ NullPointerException
// Нельзя вызвать метод на null
Сценарий 2: Передача null как параметр в equals()
String str = "hello";
str.equals(null); // ✅ Вернет false, не выбросит исключение
// Это ПРАВИЛЬНОЕ поведение согласно контракту equals()
Контракт equals() в Java
Согласно документации Object.equals():
Метод equals() должен реализовывать отношение эквивалентности:
1. Рефлексивность: x.equals(x) == true
2. Симметричность: если x.equals(y) == true, то y.equals(x) == true
3. Транзитивность: если x.equals(y) && y.equals(z), то x.equals(z)
4. Консистентность: несколько вызовов дают одинаковый результат
5. null сравнение: x.equals(null) должен вернуть false
Правильная Реализация equals()
public class User {
private final String id;
private final String name;
public User(String id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object o) {
// Проверка 1: сравниваем с null
if (o == null) {
return false; // ✅ Правильно: null никогда не равен объекту
}
// Проверка 2: проверяем тип
if (!(o instanceof User)) {
return false; // Разные типы не равны
}
// Проверка 3: приводим к нужному типу и сравниваем
User other = (User) o;
return this.id.equals(other.id) && this.name.equals(other.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
// Использование:
User user1 = new User("1", "John");
User user2 = new User("1", "John");
User user3 = null;
user1.equals(user2); // true
user1.equals(user3); // false (не NullPointerException!)
user1.equals("string"); // false
Частая Ошибка: Забыли проверить null
// ❌ НЕПРАВИЛЬНО
public class BadUser {
private String id;
@Override
public boolean equals(Object o) {
User other = (User) o; // ❌ Если o == null, будет NullPointerException
return this.id.equals(other.id); // ❌ Если other.id == null, ошибка
}
}
// Использование:
BadUser user1 = new BadUser("1");
user1.equals(null); // ❌ NullPointerException!
// ✅ ПРАВИЛЬНО
public class GoodUser {
private String id;
@Override
public boolean equals(Object o) {
if (o == null || !(o instanceof GoodUser)) {
return false; // Правильная обработка null
}
GoodUser other = (GoodUser) o;
return this.id.equals(other.id);
}
}
// Использование:
GoodUser user1 = new GoodUser("1");
user1.equals(null); // false (правильно)
Использование Objects.equals()
Существует утилита для правильного сравнения:
import java.util.Objects;
public class Modern User {
private String id;
private String name;
@Override
public boolean equals(Object o) {
if (!(o instanceof User)) {
return false;
}
User other = (User) o;
// Objects.equals() обрабатывает null автоматически
return Objects.equals(this.id, other.id) &&
Objects.equals(this.name, other.name);
}
}
// Objects.equals(a, b) эквивалентно:
// (a == b) || (a != null && a.equals(b))
// Это обрабатывает случаи когда a или b == null
Использование @EqualsAndHashCode (Lombok)
Для упрощения можно использовать Lombok:
import lombok.EqualsAndHashCode;
@EqualsAndHashCode
public class User {
private String id;
private String name;
// Lombok автоматически генерирует equals() и hashCode()
// с правильной обработкой null
}
// Генерируемый код выглядит так:
// public boolean equals(Object o) {
// if (o == this) return true;
// if (!(o instanceof User)) return false;
// User other = (User) o;
// return Objects.equals(this.id, other.id) &&
// Objects.equals(this.name, other.name);
// }
Практические Примеры
Пример 1: Коллекции и null
List<String> list = Arrays.asList("hello", null, "world");
// Это работает благодаря правильной реализации equals()
boolean contains = list.contains(null); // true
// Внутри List.contains() делает:
// for (Object o : list) {
// if (o == null) { // Правильная проверка
// return true;
// }
// }
Пример 2: HashMap и null ключи
Map<User, String> map = new HashMap<>();
User user1 = new User("1", "John");
User user2 = new User("1", "John");
map.put(user1, "data1");
map.get(user2); // Вернет "data1", если equals() правильно реализован
// HashMap использует equals() для поиска:
// if (key == null) {
// // обработка null ключей
// } else if (key.equals(cachedKey)) {
// // найдены
// }
Пример 3: Stream операции
List<User> users = Arrays.asList(
new User("1", "John"),
new User("2", "Jane"),
null
);
// filter работает корректно с null благодаря equals()
users.stream()
.filter(u -> u.equals(new User("1", "John")))
.findFirst(); // Найдет первого User
// Если null в коллекции, filter просто его пропустит
users.stream()
.filter(u -> u != null && u.getName().equals("John"))
.findFirst(); // Правильно обрабатывает null
equals() vs ==
String str1 = "hello";
String str2 = new String("hello");
str1 == str2; // false (разные объекты в памяти)
str1.equals(str2); // true (одинаковое содержимое)
str1 == null; // false
str1.equals(null); // false
null == null; // true
// При работе с объектами:
User user1 = new User("1", "John");
User user2 = new User("1", "John");
User user3 = user1;
User user4 = null;
user1 == user2; // false (разные ссылки)
user1.equals(user2); // true (одинаковое содержимое)
user1 == user3; // true (одна и та же ссылка)
user1.equals(user3); // true
user1 == user4; // false
user1.equals(user4); // false (не NullPointerException!)
Правило: Всегда Проверяй null
public class Rule {
@Override
public boolean equals(Object o) {
// Шаг 1: Проверь null
if (o == null) {
return false;
}
// Шаг 2: Проверь тип
if (!(o instanceof Rule)) {
return false;
}
// Шаг 3: Приведи тип и сравни поля
Rule other = (Rule) o;
return this.field1.equals(other.field1);
}
}
// Или современный способ:
public class ModernRule {
@Override
public boolean equals(Object o) {
return (o instanceof ModernRule other) &&
Objects.equals(this.field1, other.field1);
}
}
Заключение
-
Вызов метода на null выбросит NullPointerException
null.equals(obj); // ❌ NullPointerException -
Правильная реализация equals() возвращает false для null
obj.equals(null); // ✅ false -
Всегда проверяй null первым делом в equals()
if (o == null) return false; -
Используй Objects.equals() для безопасного сравнения
Objects.equals(a, b); // Безопасно обрабатывает null -
Это критично для работы Collections и Maps
- List.contains() полагается на equals()
- HashMap.get() полагается на equals()
- Stream.filter() полагается на equals()
Неправильная реализация equals() может привести к неожиданному поведению в production коде!