Почему в Java объекты разных классов не будут равными, даже если все их поля имеют одинаковые значения?

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

Ответ

Потому что контракт метода Object.equals() подразумевает, что объекты должны быть сравнимы только внутри своей иерархии наследования. Основные причины:

  1. Разная семантика (поведение): Классы определяют не только данные (поля), но и поведение (методы). Объекты с одинаковыми данными, но разным поведением, по смыслу не эквивалентны.
  2. Стандартная реализация equals(): По умолчанию (в классе Object) и в большинстве корректных реализаций используется проверка на точное совпадение типов (например, this.getClass() != obj.getClass()).

Пример:

class Point {
    private int x, y;
    // конструктор, геттеры
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        // Проверка на точное совпадение класса
        if (o == null || getClass() != o.getClass()) return false;
        Point point = (Point) o;
        return x == point.x && y == point.y;
    }
}

class ColoredPoint extends Point {
    private Color color;
    // конструктор, геттеры
    @Override
    public boolean equals(Object o) {
        // Также требует точного совпадения класса ColoredPoint
        if (!super.equals(o)) return false;
        if (o == null || getClass() != o.getClass()) return false;
        ColoredPoint that = (ColoredPoint) o;
        return Objects.equals(color, that.color);
    }
}

Point p = new Point(1, 2);
ColoredPoint cp = new ColoredPoint(1, 2, Color.RED);
System.out.println(p.equals(cp)); // false - разные классы

Нарушение этого правила (сравнение объектов разных классов) ведёт к нарушению ключевых свойств equals(): симметричности (a.equals(b) == b.equals(a)) и транзитивности.