Должно ли сохраняться равенство (equals) объектов после изменения состояния одного из них?

«Должно ли сохраняться равенство (equals) объектов после изменения состояния одного из них?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, если два объекта равны по equals(), они должны оставаться равными, пока их состояние, участвующее в сравнении, не изменится. Нарушение этого правила ведет к некорректной работе коллекций, основанных на хэшах (HashMap, HashSet).

Пример нарушения с изменяемым (mutable) объектом:

public final class MutableKey {
    private int id;
    public MutableKey(int id) { this.id = id; }
    public void setId(int id) { this.id = id; } // Сеттер меняет состояние!

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof MutableKey)) return false;
        MutableKey that = (MutableKey) o;
        return id == that.id; // Сравнение по полю `id`
    }
    @Override
    public int hashCode() { return Objects.hash(id); }
}

// Демонстрация проблемы
MutableKey key1 = new MutableKey(1);
MutableKey key2 = new MutableKey(1);

Set<MutableKey> set = new HashSet<>();
set.add(key1);
System.out.println(set.contains(key2)); // true (объекты равны)

key1.setId(2); // Меняем состояние объекта, уже лежащего в множестве!
System.out.println(set.contains(key1)); // false! Объект "потерялся"
// Хэш-бакет изменился, но объект не перераспределился.

Решение:

  • Используйте неизменяемые (immutable) объекты в качестве ключей (String, Integer, собственные immutable классы).
  • Если объект должен быть изменяемым, не изменяйте поля, участвующие в equals()/hashCode(), после помещения объекта в hash-коллекцию.