В каком классе изначально появляется equals
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# В каком классе изначально появляется метод equals
Краткий ответ
Метод equals() изначально определён в классе java.lang.Object, который является базовым классом для всех классов в Java.
public class Object {
public boolean equals(Object obj) {
return (this == obj);
}
}
Это означает, что каждый класс в Java наследует метод equals от Object, если не переопределит его.
Наследование equals
// Object
public boolean equals(Object obj) {
return (this == obj); // Сравнение по ссылкам по умолчанию
}
// String переопределяет
public boolean equals(Object obj) {
if (this == obj) // Если одна и та же ссылка
return true;
if (obj == null || !(obj instanceof String))
return false;
String aString = (String)obj;
// Сравнивает содержимое, не ссылки
}
// Integer переопределяет
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
Дефолтное поведение (из Object)
По умолчанию метод equals использует оператор == — сравнивает ссылки (identity):
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// НЕ переопределяем equals
}
Person p1 = new Person("John", 25);
Person p2 = new Person("John", 25);
System.out.println(p1.equals(p2)); // false!
// Потому что это разные объекты в памяти
// Даже если имеют одинаковые значения
System.out.println(p1 == p2); // false (то же самое)
System.out.println(p1.equals(p1)); // true (одна и та же ссылка)
Почему нужно переопределять equals
Стандартное поведение часто не подходит:
// Хотим сравнивать Person по содержимому, не по ссылке
@Override
public boolean equals(Object obj) {
// Проверка на null и тип
if (obj == null || !(obj instanceof Person)) {
return false;
}
Person other = (Person) obj;
// Сравниваем значения
return this.name.equals(other.name) && this.age == other.age;
}
Person p1 = new Person("John", 25);
Person p2 = new Person("John", 25);
System.out.println(p1.equals(p2)); // true! Теперь сравниваются значения
Contract equals в Java
Когда переопределяешь equals, нужно соблюдать контракт:
1. Reflective — сравнение с собой
Person p = new Person("John", 25);
assert p.equals(p); // Должно быть true
2. Symmetric — если a.equals(b), то b.equals(a)
Person p1 = new Person("John", 25);
Person p2 = new Person("John", 25);
assert p1.equals(p2) == p2.equals(p1); // Должно быть одинаково
3. Transitive — если a.equals(b) и b.equals(c), то a.equals(c)
if (a.equals(b) && b.equals(c)) {
assert a.equals(c); // Должно быть true
}
4. Consistent — несколько вызовов дают одинаковый результат
Person p1 = new Person("John", 25);
Person p2 = new Person("John", 25);
assert p1.equals(p2) == p1.equals(p2); // Всегда одинаково
5. Null comparison — x.equals(null) должно быть false
Person p = new Person("John", 25);
assert !p.equals(null); // Должно быть false
Правильная реализация equals
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
// 1. Проверка на null
if (obj == null) {
return false;
}
// 2. Проверка, что это один и тот же объект
if (this == obj) {
return true;
}
// 3. Проверка типа
if (!(obj instanceof Person)) {
return false;
}
// 4. Кастинг и сравнение полей
Person other = (Person) obj;
return this.name.equals(other.name) &&
this.age == other.age;
}
// Если переопределяешь equals, ОБЯЗАТЕЛЬНО переопредели hashCode!
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
Вариант с Java 14+ (Records)
Java 14 ввела Records, которые автоматически генерируют equals и hashCode:
public record Person(String name, int age) {
// equals, hashCode, toString генерируются автоматически!
}
Person p1 = new Person("John", 25);
Person p2 = new Person("John", 25);
System.out.println(p1.equals(p2)); // true
IDE автогенерация
В IntelliJ IDEA можно автоматически сгенерировать equals и hashCode:
Code → Generate → equals() and hashCode()
Оно создаст что-то вроде:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
Связь с hashCode
ВАЖНО: если переопределяешь equals, ОБЯЗАТЕЛЬНО переопредели hashCode!
Почему? Потому что equals и hashCode должны быть согласованы:
if (a.equals(b)) {
assert a.hashCode() == b.hashCode(); // Контракт!
}
Это особенно важно когда используешь объект в HashMap, HashSet и т.д.:
// ❌ Плохо
public class Person {
private String name;
@Override
public boolean equals(Object obj) {
// Переопределяем equals
}
// ❌ НО НЕ переопределяем hashCode
}
Set<Person> set = new HashSet<>();
Person p1 = new Person("John");
Person p2 = new Person("John");
set.add(p1);
set.add(p2);
// p1.equals(p2) == true
// НО p1 и p2 имеют разные hashCode
// Оба добавятся в set! (Ошибка!)
assert set.size() == 1; // Ожидаем 1, получаем 2!
// ✅ Правильно
public class Person {
private String name;
@Override
public boolean equals(Object obj) {
// ...
}
@Override
public int hashCode() {
return Objects.hash(name); // Соответствует equals
}
}
Примеры из Java Library
String
public final class String implements Comparable<String>, CharSequence {
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
}
Integer
public final class Integer extends Number implements Comparable<Integer> {
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
}
Типичные ошибки
❌ Ошибка 1: Неправильная сигнатура
// Это НЕ переопределение, а перегрузка!
@Override
public boolean equals(Person obj) {
// ...
}
// Правильно:
@Override
public boolean equals(Object obj) {
// ...
}
❌ Ошибка 2: Не переопределить hashCode
public class Person {
@Override
public boolean equals(Object obj) { /* ... */ }
// ❌ hashCode не переопределён
}
❌ Ошибка 3: Изменяемые поля
public class Person {
private String name; // Changeable!
@Override
public boolean equals(Object obj) {
Person other = (Person) obj;
return this.name.equals(other.name);
}
}
Person p = new Person("John");
Set<Person> set = new HashSet<>();
set.add(p);
p.name = "Jane"; // Меняем name
// Теперь set.contains(p) вернёт false!
// Потому что hashCode изменился
Тестирование equals
public class PersonTest {
@Test
public void testEquals() {
Person p1 = new Person("John", 25);
Person p2 = new Person("John", 25);
Person p3 = new Person("Jane", 26);
// Reflective
assertEquals(p1, p1);
// Symmetric
assertEquals(p1, p2);
assertEquals(p2, p1);
// Consistent
assertEquals(p1, p2);
assertEquals(p1, p2);
// Null comparison
assertNotEquals(p1, null);
// Different types
assertNotEquals(p1, "John");
// Different values
assertNotEquals(p1, p3);
}
@Test
public void testEqualsAndHashCode() {
Person p1 = new Person("John", 25);
Person p2 = new Person("John", 25);
if (p1.equals(p2)) {
assertEquals(p1.hashCode(), p2.hashCode());
}
}
}
Заключение
Метод equals() изначально определён в классе Object и по умолчанию сравнивает объекты по ссылкам (identity). При необходимости сравнивать по содержимому (equality) нужно переопределить equals(). ВАЖНО: при переопределении equals() обязательно переопредели и hashCode() для соответствия контракту Java.