Object-Oriented Programming
equals() and hashCode()
Deconstruct the equivalence contract (reflexive, symmetric, transitive), hash collision costs, and HashMap bucket resolutions.
Interview: Focuses on equals/hashCode contract invariants, hash collision degradation, and correct contract overrides.
The equals() and hashCode() contract governs object comparison and hash-based collections in Java. Violating this contract causes silent data corruption in HashMap and HashSet.
Core Idea
If two objects are equal according to equals(), they must return the same hashCode() integer. The reverse is not required.
Why It Matters
Failing to override hashCode alongside equals makes objects disappear from HashMaps, as lookups search the wrong buckets.
Interview Lens
Tests equals properties (reflexivity, symmetry, transitivity), hash collision resolution steps, and HashMap bucket distributions.
The Equivalence Contract
An implementation of equals() must satisfy five equivalence properties:
- Reflexive:
x.equals(x)must returntrue. - Symmetric: If
x.equals(y)istrue, theny.equals(x)must returntrue. - Transitive: If
x.equals(y)istrueandy.equals(z)istrue, thenx.equals(z)must returntrue. - Consistent: Repeated invocations of
equals()must return the same result, provided no information used in comparison is modified. - Null-Comparison:
x.equals(null)must returnfalsefor any non-null referencex.
Hash Collisions and HashMap Bucket Resolutions
When you put an object in a HashMap, the map uses the object's hashCode() to calculate which bucket index to store the entry in.
Hash Collision: Occurs when two unequal objects return the same hashcode. They resolve to the same bucket index:
- Chaining: The bucket stores colliding elements in a linked list. Lookup costs degrade from
O(1)toO(N). - Treeification (Java 8+): If a bucket's collision list exceeds a threshold (8 elements) and the map's total capacity is at least 64, the JVM converts the list into a self-balancing Red-Black Tree, improving lookup costs to
O(log N).
Common Pitfalls
- Overriding equals without hashCode: The classic error. Objects are added to the map but cannot be found during lookups because they search different buckets.
- Using mutable fields in hashcodes: Modifying fields that are used to compute the hashCode after the object has been added to a Map. The object's bucket index changes, making it lost inside the map.
- Incorrect Parameter Type: Writing
public boolean equals(User other)instead of overridingpublic boolean equals(Object other), which overloads the method instead of overriding it.
Best Practices
- Always override
hashCode()whenever you overrideequals(). Use the same fields for both calculations. - Use
Objects.equals(a, b)andObjects.hash(fields...)to write clean, null-safe overrides. - Use immutable fields (like IDs) for equals and hashCode calculations. Avoid using mutable fields to prevent runtime lookup failures.
Interview-Relevant Information
Q1: What happens if you override equals() but not hashCode()?
Answer: It breaks the contract. Two objects that are equal could return different hashcodes. When stored in a HashMap, the objects may resolve to different buckets, allowing duplicate keys or causing get() lookups to return null.
Q2: How does HashMap resolve hash collisions in Java 8 and later?
Answer: HashMap resolves collisions using chaining. If the number of colliding elements in a bucket exceeds 8 and the map's total capacity is at least 64, the linked list is converted to a Red-Black Tree, improving search times from O(N) to O(log N).
Quick Checklist
Can you implement the equals contract, write null-safe comparisons, explain HashMap treeification thresholds, and prevent lost keys in Maps? If yes, you understand equals and hashCode.
Use Cases
Implementing database entity keys that map to unique lookup collections.
Designing secure caching keys inside memory-bounded tables.
Common Mistakes
Overriding equals() but failing to define a matching hashCode() method.
Using mutable properties in hash calculations, causing keys to get lost in HashMaps.