Почему этот data class ведёт себя неожиданно как ключ map?
Два значения User выглядят равными, но map считает их двумя ключами. Объясните вывод и поправьте User, чтобы равные пользователи схлопывались в один ключ.
class Address(val city: String)
data class User(val name: String, val address: Address) {
val id = Random().nextInt()
}
val u1 = User("Ivan", Address("Moscow"))
val u2 = User("Ivan", Address("Moscow"))
val map = mapOf(u1 to "A", u2 to "B")
println(map.size) // ?
println(map[User("Ivan", Address("Moscow"))]) // ?
Определите причину и исправьте.
data class генерирует equals/hashCode только из свойств первичного конструктора, исключая id из тела. Виноват Address: обычный класс сравнивается по ссылке, поэтому пользователи не равны. Вывод — 2, затем null. Решение — сделать Address тоже data class.
- ✗Думать, что равенство
data classохватывает свойства из тела, такие какid - ✗Считать, что вложенное не-
dataполе сравнивается по значению автоматически - ✗Полагать, что
mapOfдедуплицирует ключи иначе, чемHashMap
- →Для каких свойств генерируемая функция
copyпринимает параметры? - →Почему
hashCodeобязан согласовываться сequalsдля корректного поиска в map?
Баг
data class генерирует equals/hashCode только по свойствам первичного конструктора. Поле id, объявленное в теле, в равенство не входит. Но проблема не в id: Address — обычный класс, поэтому сравнивается по ссылке (identity). Два литерала Address("Moscow") — разные объекты, значит u1 != u2.
class Address(val city: String) // ❌ identity equals
data class User(val name: String, val address: Address) {
val id = Random().nextInt() // не входит в equals/hashCode
}
Вывод: map.size → 2 (ключи различны), map[User(...)] → null (новый Address снова не равен).
Исправление
Сделайте Address тоже data class — он получит структурное равенство по city.
data class Address(val city: String) // ✅ value equals
Теперь u1 == u2, map.size → 1, а поиск по эквивалентному ключу находит значение.