← Назад к вопросам

В каком классе изначально появляется equals

1.0 Junior🔥 201 комментариев
#ООП#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# В каком классе изначально появляется метод 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.