← Назад к вопросам

Что будет, если объект, вызвавший equals, равен null

2.0 Middle🔥 101 комментариев
#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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);
    }
}

Заключение

  1. Вызов метода на null выбросит NullPointerException

    null.equals(obj); // ❌ NullPointerException
    
  2. Правильная реализация equals() возвращает false для null

    obj.equals(null); // ✅ false
    
  3. Всегда проверяй null первым делом в equals()

    if (o == null) return false;
    
  4. Используй Objects.equals() для безопасного сравнения

    Objects.equals(a, b); // Безопасно обрабатывает null
    
  5. Это критично для работы Collections и Maps

    • List.contains() полагается на equals()
    • HashMap.get() полагается на equals()
    • Stream.filter() полагается на equals()

Неправильная реализация equals() может привести к неожиданному поведению в production коде!

Что будет, если объект, вызвавший equals, равен null | PrepBro