Explain how a data class's generated `equals` and `hashCode` satisfy the equals/hashCode contract, and what happens if a primary-constructor property is an array.
answer
- Same props -> equal implies equal hash
- Array.equals is referential, not content
- Use contentEquals/contentHashCode
- Or model as List for content equality
- val keys to keep hashCode stable
basics
~20 sThe generated equals and hashCode use the same set of constructor properties, so equal objects always produce equal hashes — that's the contract. But arrays are compared by reference, so two data objects holding equal-content arrays can wrongly come out unequal.
solid answer
~30 sThe compiler generates `equals` and `hashCode` from the same primary-constructor properties, guaranteeing the core contract: if `a == b` then `a.hashCode() == b.hashCode()`. `equals` is reflexive, symmetric, transitive, and consistent because it just `&&`-compares each property with `==` and combines hashes deterministically. The trap is reference types whose own `equals` is identity-based — most notably `Array`. `Array.equals` is referential, so `data class Holder(val data: IntArray)` will report two holders with content-equal arrays as unequal, and their hashes differ. The fix is to override `equals`/`hashCode` manually using `contentEquals`/`contentHashCode` (or `contentDeepEquals` for nested arrays), or model the field as a `List` instead.
code
kotlin · 5 linesdata class A(val xs: IntArray)
data class B(val xs: List<Int>)
println(A(intArrayOf(1)) == A(intArrayOf(1))) // false (array identity)
println(B(listOf(1)) == B(listOf(1))) // true (List content equality)go deeper
Knows equal objects should have equal hash codes.
Can state the full contract and that generated members satisfy it via the shared property set.
Identifies the array referential-equality trap and fixes it with contentEquals/contentHashCode or List.
Reasons about API/data-modeling choices (List vs array) and consistency guarantees across serialization and collection use.
## The equals/hashCode contract Java/Kotlin's contract requires: 1. **Reflexive**: `a == a` is true. 2. **Symmetric**: `a == b` iff `b == a`. 3. **Transitive**: if `a == b` and `b == c` then `a == c`. 4. **Consistent**: repeated calls give the same result if state is unchanged. 5. **hashCode consistency**: if `a == b` then `a.hashCode() == b.hashCode()`. ## How generated members satisfy it The generated `equals` essentially does: ```kotlin override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is User) return false return name == other.name && age == other.age } ``` and `hashCode` combines the same properties (`31 * name.hashCode() + age`). Because both use the **same property set** with the **same `==`/`hashCode`**, equal objects necessarily hash equally — point 5 holds for free. The boolean `&&` chain makes it reflexive/symmetric/transitive as long as each property's own `equals` obeys the contract. ## The array trap `Array` (and `IntArray`, etc.) defines `equals` as **referential identity**, not content. So: ```kotlin data class Holder(val data: IntArray) val h1 = Holder(intArrayOf(1, 2)) val h2 = Holder(intArrayOf(1, 2)) println(h1 == h2) // false! arrays compared by reference ``` The generated `equals` faithfully calls `data == other.data`, which for arrays is reference comparison — so content-equal holders are unequal, and `hashCode` differs too. The contract is technically still satisfied (it's consistent with array identity), but the behavior surprises everyone. ## Fixes - **Override manually** using content-aware functions: ```kotlin data class Holder(val data: IntArray) { override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is Holder) return false return data.contentEquals(other.data) } override fun hashCode(): Int = data.contentHashCode() } ``` Use `contentDeepEquals`/`contentDeepHashCode` for arrays of arrays. - **Prefer `List`**: `data class Holder(val data: List<Int>)` — `List.equals` is content-based, so generated members 'just work'. ## Mutability caveat `hashCode` consistency assumes the property values don't change while the object lives in a hash structure. Mutating a `var` constructor property after insertion into a `HashSet`/`HashMap` corrupts lookups. Favor `val` for keys.
- Why doesn't the compiler just use contentEquals automatically for array properties?Generated equals uniformly calls each property's own equals; arrays define identity equals, and the compiler doesn't special-case them — it only warns and leaves the override to you.
- Does Kotlin warn about array properties in data classes?Yes — the compiler emits a warning suggesting you override equals/hashCode or use contentEquals when a data class has an array property used in equals.
saying these in an interview costs you the question
- Believes arrays compare by content in data class equals
- Cannot state the hashCode-consistency rule
- Overrides equals but forgets to override hashCode to match
- Uses contentEquals for nested arrays instead of contentDeepEquals
- Thinks generated equals violates the contract for arrays (it's consistent, just identity-based)