Равенство и equals
1. Операторы сравнения
1.1 Оператор ==
- Сравнивает ссылки на объекты
- Проверяет, указывают ли две ссылки на один и тот же объект в памяти
- Для примитивных типов сравнивает значения
1.2 Метод equals()
- Сравнивает содержимое объектов
- Наследуется от класса Object
- По умолчанию работает так же, как ==
- Требует переопределения для правильного сравнения объектов
2. Контракт метода equals()
2.1 Требования к реализации
- Рефлексивность: x.equals(x) должно возвращать true
- Симметричность: если x.equals(y) == true, то y.equals(x) == true
- Транзитивность: если x.equals(y) == true и y.equals(z) == true, то x.equals(z) == true
- Согласованность: повторные вызовы equals() должны возвращать одинаковый результат
- Сравнение с null: x.equals(null) должно возвращать false
3. Правильная реализация equals()
3.1 Базовый шаблон
@Override
public boolean equals(Object obj) {
// 1. Проверка на ссылку на себя
if (this == obj) return true;
// 2. Проверка на null
if (obj == null) return false;
// 3. Проверка на принадлежность к тому же классу
if (getClass() != obj.getClass()) return false;
// 4. Приведение типов
MyClass other = (MyClass) obj;
// 5. Сравнение значимых полей
return field1.equals(other.field1) &&
field2.equals(other.field2);
}
3.2 Пример с разными типами полей
public class Student {
private String name;
private int id;
private double avgGrade;
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Student other = (Student) obj;
return id == other.id &&
Double.compare(avgGrade, other.avgGrade) == 0 &&
Objects.equals(name, other.name);
}
}
4. Особые случаи
4.1 Сравнение массивов
public class ArrayContainer {
private int[] numbers;
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
ArrayContainer other = (ArrayContainer) obj;
return Arrays.equals(numbers, other.numbers);
}
}
4.2 Сравнение с учётом наследования
public class Shape {
private String color;
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Shape)) return false;
Shape other = (Shape) obj;
return Objects.equals(color, other.color);
}
}
public class Circle extends Shape {
private double radius;
@Override
public boolean equals(Object obj) {
if (!super.equals(obj)) return false;
if (!(obj instanceof Circle)) return false;
Circle other = (Circle) obj;
return Double.compare(radius, other.radius) == 0;
}
}
5. Связь с hashCode()
- При переопределении equals() всегда нужно переопределять hashCode()
- Если equals() возвращает true, то hashCode() должен вернуть одинаковые значения
- Если equals() возвращает false, то hashCode() желательно должен вернуть разные значения
public class Person {
private String name;
private int age;
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person other = (Person) obj;
return age == other.age &&
Objects.equals(name, other.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
6. Типичные ошибки
- Нарушение симметричности при сравнении с подклассами
- Изменение сигнатуры метода (использование другого параметра вместо Object)
- Отсутствие проверки на null
- Неправильное сравнение чисел с плавающей точкой
- Забытое переопределение hashCode()
7. Лучшие практики
- Всегда использовать @Override
- Проверять значения на null через Objects.equals()
- Использовать Double.compare() для сравнения double
- Применять Arrays.equals() для массивов
- Включать все значимые поля в сравнение
- Соблюдать контракт equals()
- Всегда переопределять hashCode() вместе с equals()
8. Инструменты
- IDE (автогенерация equals() и hashCode())
- Apache Commons Lang (класс EqualsBuilder)
- Lombok (@EqualsAndHashCode)
- Objects.equals() и Objects.hash() из стандартной библиотеки
примеры
[[Programming/java/1. osnovi/Тема 5. Интерфейсы, абстрактные классы, статические методы/Урок 3. Равенство и equals/задача|задача]]