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

Как equals сравнивает объекты

1.0 Junior🔥 171 комментариев
#Kotlin основы

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

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

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

Как equals() сравнивает объекты

equals() — это метод для сравнения содержимого двух объектов. По умолчанию он сравнивает ссылки (как ==), но можно переопределить для сравнения значений.

Стандартная реализация в Object

public boolean equals(Object obj) {
    return this == obj;  // Сравниваются ссылки, не содержимое
}

По умолчанию equals() работает как оператор == — сравнивает, указывают ли оба параметра на ОДНОТот же объект в памяти.

Пример: стандартное сравнение

class User {
    val name: String
    val age: Int
    
    constructor(name: String, age: Int) {
        this.name = name
        this.age = age
    }
}

val user1 = User("John", 30)
val user2 = User("John", 30)

println(user1 == user2)      // false (разные объекты в памяти)
println(user1.equals(user2)) // false (стандартная реализация)
println(user1 === user2)     // false (разные ссылки)

Переопределение equals() — сравнение значений

Чтобы сравнивать содержимое, нужно переопределить equals():

class User(
    val name: String,
    val age: Int
) {
    override fun equals(other: Any?): Boolean {
        // 1. Проверка типа
        if (other !is User) return false
        
        // 2. Сравнение полей
        return name == other.name && age == other.age
    }
    
    override fun hashCode(): Int {
        // ВАЖНО: если переопределил equals, переопредели и hashCode!
        return name.hashCode() * 31 + age.hashCode()
    }
}

val user1 = User("John", 30)
val user2 = User("John", 30)

println(user1 == user2)      // true (одинаковые значения)
println(user1.equals(user2)) // true
println(user1 === user2)     // false (разные объекты)

Data Class — автоматическое equals()

В Kotlin data class автоматически генерирует equals(), hashCode() и toString():

data class User(
    val name: String,
    val age: Int
)

val user1 = User("John", 30)
val user2 = User("John", 30)

println(user1 == user2) // true (сгенерирован equals)
println(user1.hashCode() == user2.hashCode()) // true

Таблица: == vs equals() vs ===

ОператорЧто проверяетПереопределяется
==Вызывает equals()Да (переопределяется в классе)
equals()По умолчанию ссылки, можно переопределитьДа
===Ссылки (идентичность)Нет (всегда сравнивает адреса)

Правила переопределения equals()

Эти правила обязательны (контракт equals/hashCode):

1. Рефлексивность: x.equals(x) должен быть true

val user = User("John", 30)
println(user.equals(user)) // true

2. Симметричность: если x.equals(y), то y.equals(x)

val user1 = User("John", 30)
val user2 = User("John", 30)
println(user1.equals(user2)) // true
println(user2.equals(user1)) // true (симметрично)

3. Транзитивность: если x.equals(y) и y.equals(z), то x.equals(z)

val user1 = User("John", 30)
val user2 = User("John", 30)
val user3 = User("John", 30)
println(user1.equals(user2) && user2.equals(user3)) // true
println(user1.equals(user3)) // true (транзитивно)

4. Согласованность с hashCode()

Если два объекта равны (equals() = true), их hashCode() ДОЛЖНЫ быть одинаковыми:

if (obj1.equals(obj2)) {
    require(obj1.hashCode() == obj2.hashCode())
}

Правильная реализация equals()

class Person(
    val name: String,
    val age: Int,
    val email: String
) {
    override fun equals(other: Any?): Boolean {
        // 1. Проверка null
        if (other == null) return false
        
        // 2. Проверка типа
        if (other !is Person) return false
        
        // 3. Сравнение всех значимых полей
        return name == other.name &&
               age == other.age &&
               email == other.email
    }
    
    override fun hashCode(): Int {
        // Используем все поля, что в equals
        var result = name.hashCode()
        result = 31 * result + age.hashCode()
        result = 31 * result + email.hashCode()
        return result
    }
}

Ошибки при переопределении equals()

❌ Ошибка 1: забыл hashCode()

class User(val name: String) {
    override fun equals(other: Any?): Boolean {
        return other is User && name == other.name
    }
    // hashCode НЕ переопределён!
}

val map = mutableMapOf<User, String>()
val user1 = User("John")
val user2 = User("John")

map[user1] = "value1"
println(map[user2]) // null! (разные hashCode)
println(user1.equals(user2)) // true (но в HashMap по разным клавишам!)

❌ Ошибка 2: сравнение по ссылке вместо значений

class User(val name: String) {
    override fun equals(other: Any?): Boolean {
        return this === other // Ошибка! Работает как Object.equals
    }
}

val user1 = User("John")
val user2 = User("John")
println(user1.equals(user2)) // false (разные ссылки)

❌ Ошибка 3: сравнение не всех полей

data class User(
    val name: String,
    val age: Int,
    val password: String
) {
    override fun equals(other: Any?): Boolean {
        if (other !is User) return false
        return name == other.name && age == other.age
        // password забыли! Это нарушает контракт
    }
}

equals() в коллекциях

equals() используется при поиске в коллекциях:

data class Product(val id: Int, val name: String)

val list = listOf(
    Product(1, "Phone"),
    Product(2, "Laptop"),
    Product(3, "Tablet")
)

val search = Product(2, "Laptop")
println(search in list) // true (использует equals)

// Внутри используется цикл:
for (item in list) {
    if (item.equals(search)) {
        return true
    }
}

equals() vs compareTo()

МетодДля чегоВозвращает
equals()РавенствоBoolean
compareTo()Упорядочение-1 (меньше), 0 (равно), 1 (больше)
data class Version(val major: Int, val minor: Int) : Comparable<Version> {
    override fun equals(other: Any?): Boolean {
        return other is Version && major == other.major && minor == other.minor
    }
    
    override fun compareTo(other: Version): Int {
        return when {
            major != other.major -> major.compareTo(other.major)
            else -> minor.compareTo(other.minor)
        }
    }
}

val v1 = Version(1, 2)
val v2 = Version(1, 2)
val v3 = Version(1, 3)

println(v1 == v2)      // true (equals)
println(v1 < v3)       // true (compareTo)
println(v1.compareTo(v2)) // 0 (равны)

Итог

  • equals() по умолчанию сравнивает ссылки (как ===)
  • equals() можно переопределить для сравнения значений
  • При переопределении equals() ОБЯЗАТЕЛЬНО переопредели hashCode()
  • data class автоматически генерирует equals() и hashCode()
  • == вызывает equals(), а === сравнивает ссылки
  • Правила: рефлексивность, симметричность, транзитивность, согласованность с hashCode()