If you override equals() for == to behave correctly in a HashSet/HashMap, what else must you do and why?
answer
- Override equals() => must override hashCode()
- a == b implies a.hashCode() == b.hashCode()
- Collections: hashCode() picks bucket, equals() confirms
- data class generates both from primary-constructor props
- Keep equals/hashCode fields immutable (val)
basics
~10 sYou must also override hashCode() so equal objects have the same hash. Hash-based collections use hashCode() to find the bucket, then equals() to confirm. Override one without the other and lookups break.
solid answer
~40 s`==` calls `equals()`, but hash-based collections (`HashSet`, `HashMap`, `LinkedHashMap`) first call `hashCode()` to pick a bucket, then `equals()` within it. The contract: if `a == b` then `a.hashCode() == b.hashCode()`. Override `equals()` without `hashCode()` and two equal objects can land in different buckets, so `set.contains(equalObject)` returns `false` and map lookups miss. Both methods must derive from the **same** fields. In Kotlin, a `data class` auto-generates both `equals()`/`hashCode()` from the **primary-constructor** properties, so prefer it. Also keep the fields used effectively **immutable** — mutating a field after insertion changes the hash and 'loses' the element in the collection. The reverse contract (equal hashes need not mean equal objects — collisions are allowed) is fine.
code
kotlin · 7 lines// Broken: equals without hashCode
class A(val id: Int) { override fun equals(o: Any?) = o is A && o.id == id }
println(hashSetOf(A(1)).contains(A(1))) // false
// Fixed via data class
data class B(val id: Int)
println(hashSetOf(B(1)).contains(B(1))) // truego deeper
Knows you should override hashCode() alongside equals(), or just use a data class.
States the contract (equal objects, equal hashes) and explains why hash collections need both methods.
Discusses immutability of key fields, collision handling, and that only primary-constructor props feed data class generation.
Considers consequences across the system: stable keys for caches/distributed maps, equality in domain identity vs value semantics, and audit of mutable keys.
## == leads to equals(), but collections need hashCode() too Writing `a == b` invokes `equals()`. But **hash-based collections** — `HashSet`, `HashMap`, `LinkedHashSet`, `LinkedHashMap` — don't scan every element. They: 1. Call **`hashCode()`** to compute a bucket index. 2. Within that bucket, call **`equals()`** to find the actual match. So correctness depends on both methods agreeing. ## The equals/hashCode contract - **Consistency with equals:** if `a == b` (i.e. `a.equals(b)` is true), then **`a.hashCode() == b.hashCode()` must hold**. - **Reflexive / symmetric / transitive / consistent:** `equals()` must obey these for any objects. - Equal hash codes do **not** require equal objects — **collisions are allowed** and handled by the bucket's `equals()` pass. ## What breaks if you override only equals() ```kotlin class User(val id: Long) { override fun equals(other: Any?) = other is User && other.id == id // BUG: no hashCode() override -> uses identity hash } val set = hashSetOf(User(1)) println(set.contains(User(1))) // false! different identity hash -> wrong bucket ``` Two equal `User`s get different (identity-based) hash codes, land in different buckets, and `contains` never even reaches the `equals()` check. Map keys silently fail to match. ## Do it right: data class Kotlin's `data class` generates both methods from the primary-constructor properties: ```kotlin data class User(val id: Long) // equals() + hashCode() from id val set = hashSetOf(User(1)) println(set.contains(User(1))) // true ``` Note: only **primary-constructor** properties count; a `val` declared in the body is excluded from the generated `equals`/`hashCode`. ## Immutability matters Fields used in `equals`/`hashCode` should be effectively immutable. If you insert an object then mutate such a field, its `hashCode()` changes, the collection looks in the old bucket, and the element becomes unreachable — a classic 'lost in the set' bug. Prefer `val` properties. ## Summary rule Override `equals()` and `hashCode()` **together**, from the **same** immutable fields — or let `data class` do it.
- Why does set.contains() miss when only equals() is overridden?contains() first uses hashCode() to choose a bucket. With the default identity hashCode, two equal objects hash to different buckets, so the equals() comparison never runs and the lookup fails.
- Does a body-declared val participate in a data class's generated equals()?No. Only properties in the primary constructor are used for the generated equals()/hashCode()/toString()/copy().
- Is it legal for two unequal objects to share a hashCode?Yes — that's a hash collision, which the contract permits; the bucket's equals() pass disambiguates them.
saying these in an interview costs you the question
- Overriding equals() but not hashCode()
- Deriving equals() and hashCode() from different fields
- Using mutable fields in equals/hashCode then storing in a HashSet
- Thinking equal hash codes mean equal objects
- Assuming == alone makes objects work as map keys