Why does this data class used as a map key behave unexpectedly?
Two User values look equal, yet the map treats them as two keys. Explain the printed output and fix User so equal users collapse to one key.
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"))]) // ?
Diagnose the cause and fix it.
A data class builds equals/hashCode from primary-constructor properties only, excluding the body-declared id. The culprit is Address: a plain class compared by identity, so the two users are unequal. Output 2 then null; fix by making Address a data class.
- ✗Thinking
data classequality covers body-declared properties likeid - ✗Assuming a nested non-
datafield is compared by value automatically - ✗Believing
mapOfdeduplicates keys differently from aHashMap
- →Which properties does the generated
copyfunction take parameters for? - →Why must
hashCodeagree withequalsfor correct map lookups?
The bug
A data class generates equals/hashCode from primary-constructor properties only. The body-declared id is not part of equality. But the bug is not id: Address is a plain class, so it uses identity (reference) equality. The two Address("Moscow") literals are distinct objects, so u1 != u2.
class Address(val city: String) // ❌ identity equals
data class User(val name: String, val address: Address) {
val id = Random().nextInt() // excluded from equals/hashCode
}
Output: map.size → 2 (keys differ), map[User(...)] → null (a fresh Address is again unequal).
The fix
Make Address a data class too — it then gets structural equality on city.
data class Address(val city: String) // ✅ value equals
Now u1 == u2, map.size → 1, and a lookup with an equivalent key finds the value.