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

Какое значение будет возвращено если hashCode не переопределен?

2.0 Middle🔥 101 комментариев
#JVM и память

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

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

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

hashCode() когда не переопределён

Если класс не переопределяет метод hashCode(), используется реализация по умолчанию из класса Object.

Значение по умолчанию

Java использует адрес объекта в памяти, преобразованный в целое число. Это значение основано на идентичности объекта (identity), а не на его содержимом (value).

class User  // hashCode не переопределён

val user1 = User()
val user2 = User()

println(user1.hashCode())  // Например: 588354836
println(user2.hashCode())  // Например: 1149319664
// Разные адреса в памяти = разные hashCode

Это вызывает проблемы с HashMap

data class UserWithoutHashCode(
    val id: Int,
    val name: String
)  // hashCode не переопределён

val user1 = UserWithoutHashCode(1, "John")
val user2 = UserWithoutHashCode(1, "John")

val map = mapOf(
    user1 to "value1"
)

println(map[user2])  // null! Хотя user1 и user2 логически равны

Почему null? Потому что HashMap использует hashCode() для поиска. user1 и user2 имеют разные hashCode (разные адреса памяти), поэтому HashMap их считает разными ключами.

Правильное переопределение hashCode

Правило: если переопределяешь equals(), ОБЯЗАТЕЛЬНО переопредели и hashCode()

data class User(
    val id: Int,
    val name: String
) {
    override fun equals(other: Any?): Boolean {
        if (other !is User) return false
        return id == other.id && name == other.name
    }
    
    override fun hashCode(): Int {
        return id.hashCode() xor name.hashCode()
    }
}

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

println(user1.hashCode())  // Одинаково
println(user2.hashCode())  // Одинаково

val map = mapOf(user1 to "value1")
println(map[user2])  // "value1" ✓

Как реализовать hashCode

Способ 1: Использовать data class

data class User(
    val id: Int,
    val name: String
)
// hashCode и equals автоматически переопределены

Способ 2: Вручную в обычном классе

class User(
    val id: Int,
    val name: String
) {
    override fun equals(other: Any?): Boolean {
        if (other !is User) return false
        return id == other.id && name == other.name
    }
    
    override fun hashCode(): Int {
        var result = id.hashCode()
        result = 31 * result + name.hashCode()
        return result
    }
}

Способ 3: Использовать Objects.hash() (Java 7+)

override fun hashCode(): Int {
    return Objects.hash(id, name)
}

Почему именно 31?

// 31 выбрана потому что:
// 1. Это простое число
// 2. 31 * i == (i << 5) - i  (быстрое вычисление)
// 3. Хорошо распределяет хэши

override fun hashCode(): Int {
    var result = 17  // Стартовое значение
    result = 31 * result + id.hashCode()
    result = 31 * result + name.hashCode()
    return result
}

Contract: equals и hashCode

Если два объекта equal, их hashCode ДОЛЖНЫ быть одинаковыми

// Закон
if (obj1.equals(obj2)) {
    // Это должно быть true
    obj1.hashCode() == obj2.hashCode()
}

// Но обратное не обязательно верно
// Два объекта могут иметь одинаковый hashCode но быть не equal

Пример нарушения contract

class BadUser(val id: Int) {
    override fun equals(other: Any?): Boolean {
        return id == (other as? BadUser)?.id
    }
    
    override fun hashCode(): Int {
        return 1  // ОШИБКА! Все объекты имеют одинаковый hashCode
    }
}

// В HashMap это будет очень медленно
// Все объекты будут в одной bucket
val map = mutableMapOf<BadUser, String>()
for (i in 1..1000) {
    map[BadUser(i)] = "value$i"
}
// Поиск будет O(n) вместо O(1)

Когда нужно переопределять hashCode

Всегда когда переопределяешь equals():

// Плохо
class User(val id: Int) {
    override fun equals(other: Any?): Boolean {
        return id == (other as? User)?.id
    }
    // hashCode не переопределён! ✗
}

// Хорошо
class User(val id: Int) {
    override fun equals(other: Any?): Boolean {
        return id == (other as? User)?.id
    }
    
    override fun hashCode(): Int {
        return id.hashCode()
    }
}

Использование в HashSet

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

val set = setOf(
    User(1, "John"),
    User(1, "John"),  // Дубликат
    User(2, "Jane")
)

println(set.size)  // 2 (дубликат удалён благодаря hashCode/equals)

Performance: важность хорошего hashCode

Плохое распределение:

override fun hashCode(): Int {
    return id % 10  // Только 10 разных значений!
}

Хорошее распределение:

override fun hashCode(): Int {
    return Objects.hash(id, name)  // Хорошо распределяет
}

Выводы

  1. Если не переопределён - используется адрес объекта в памяти
  2. Разные объекты - разные hashCode (даже если содержимое одинаковое)
  3. Используй data class - автоматически переопределяет hashCode
  4. Contract - если equals() true, то hashCode() должен быть одинаков
  5. Всегда переопредели оба - equals() и hashCode() вместе
  6. Performance - хорошее распределение важно для HashMap/HashSet

Это важный концепт для правильной работы с collections и equals/hashCode контрактом в Java/Kotlin.

Какое значение будет возвращено если hashCode не переопределен? | PrepBro