Метод equals() в Java: сравнение объектов и переопределение - Вопросы
Всего: 5 вопросов
1. Чем отличается операция == от метода equals() в Java?
Чем отличается операция == от метода equals() в Java?
Для ссылочных типов == проверяет идентичность: указывают ли обе переменные на один и тот же объект в памяти. Метод equals() проверяет логическое равенство: одинаковы ли объекты по смыслу, как это определено в классе.
String s1 = new String("Java");
String s2 = new String("Java");
System.out.println(s1 == s2); // false - different objects
System.out.println(s1.equals(s2)); // true - same characters
Для примитивов (int, double, boolean) используется только == — сравниваются сами значения. == также уместен для констант enum и когда нужно проверить именно совпадение ссылок. Строки, обёртки, даты и собственные классы сравнивают через equals() или Objects.equals().
2. Как правильно переопределить метод equals() в собственном классе?
Как правильно переопределить метод equals() в собственном классе?
Реализация из Object просто возвращает this == obj, то есть сравнивает ссылки. Чтобы сравнивать объекты по полям, метод переопределяют с сигнатурой equals(Object o) и аннотацией @Override. Типичные шаги:
@Override
public boolean equals(Object o) {
if (this == o) return true; // 1
if (o == null || getClass() != o.getClass()) return false; // 2
Person person = (Person) o; // 3
return age == person.age // 4
&& Objects.equals(fullName, person.fullName);
}
1) быстрая проверка на ту же ссылку; 2) null или другой класс дают false; 3) безопасное приведение типа; 4) сравнение значимых полей: примитивы через ==, ссылки через Objects.equals(), double/float через Double.compare()/Float.compare(). Вместе с equals() обязательно переопределяют hashCode() по тем же полям. IntelliJ IDEA генерирует оба метода через Code → Generate (Alt+Insert).
3. Какие свойства входят в контракт метода equals()?
Какие свойства входят в контракт метода equals()?
Для ненулевых ссылок x, y, z любая реализация equals() должна соблюдать пять свойств:
- Рефлексивность:
x.equals(x)всегдаtrue. - Симметричность:
x.equals(y)возвращает то же, чтоy.equals(x). - Транзитивность: если
x.equals(y)иy.equals(z), тоx.equals(z). - Согласованность: повторные вызовы дают один и тот же результат, пока поля, участвующие в сравнении, не меняются.
- Сравнение с null:
x.equals(null)всегдаfalseи не бросает исключение.
На эти свойства полагаются HashMap, HashSet, ArrayList.contains() и другие коллекции, поэтому их нарушение приводит к трудноуловимым ошибкам. Кроме того, равные по equals() объекты обязаны иметь одинаковый hashCode().
4. Как работает Objects.equals() и зачем он нужен, если есть a.equals(b)?
Как работает Objects.equals() и зачем он нужен, если есть a.equals(b)?
Вызов a.equals(b) бросает NullPointerException, если a равен null. Утилитный метод java.util.Objects.equals(a, b) (Java 7+) обрабатывает null сам: если оба аргумента — одна ссылка (в том числе оба null), возвращает true; если null только один — false; иначе возвращает a.equals(b).
String name = null;
// name.equals("Ivan"); // NullPointerException
System.out.println("Ivan".equals(name)); // false
System.out.println(Objects.equals(name, "Ivan")); // false
System.out.println(Objects.equals(null, null)); // true
Приём с литералом слева тоже защищает от NPE, но работает, только когда одна сторона заведомо не null. Objects.equals() универсален, поэтому его рекомендуют для сравнения ссылочных полей внутри собственного equals(). Для массивов есть Objects.deepEquals().
5. Что использовать для проверки типа в equals(): getClass() или instanceof?
Что использовать для проверки типа в equals(): getClass() или instanceof?
getClass() != o.getClass() считает равными только экземпляры точно того же класса: подкласс никогда не равен родителю, зато симметричность гарантирована. o instanceof Person пропускает и подклассы; это естественно для final-классов, но если подкласс добавляет поля и переопределяет equals(), может получиться person.equals(employee) == true и employee.equals(person) == false — нарушение симметричности.
С Java 16 проверку и приведение объединяет pattern matching, а отдельная проверка на null не нужна, так как null instanceof Person всегда false:
if (!(o instanceof Person other)) return false;
Для простых носителей данных удобнее record: компилятор сам генерирует equals(), hashCode() и toString() по всем компонентам.