Unpaired Equals / HashCode
ID |
kotlin.unpaired_equals_hashcode |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
low |
Remediation Effort |
medium |
Resource |
Code Smell |
Language |
Kotlin |
Tags |
correctness, reliability |
Description
Reports classes that override equals() without also overriding hashCode(), or that
override hashCode() without also overriding equals(). Data classes are excluded
because Kotlin auto-generates both methods for them.
Rationale
The Any contract requires that objects which are equal under equals() must return the
same value from hashCode(). Breaking this contract causes hash-based collections
(HashMap, HashSet, LinkedHashMap) to silently malfunction: two logically equal
objects may land in different buckets and the collection treats them as distinct.
// Bad — equals only
class User(val id: Int) {
override fun equals(other: Any?) = other is User && id == other.id
// hashCode not overridden — two equal Users have different hash codes
}
val set = hashSetOf(User(1))
set.contains(User(1)) // returns false — same id, different bucket
// Good — both overridden
class User(val id: Int) {
override fun equals(other: Any?) = other is User && id == other.id
override fun hashCode() = id.hashCode()
}
// Best — use a data class
data class User(val id: Int)
Remediation
Override both equals() and hashCode() together. Use a data class when the class
is a simple value holder — Kotlin generates a correct and consistent implementation
automatically. If manual implementation is required, use Objects.hash(…) to compose
hash codes from the same fields used in equals().