If Two Objects Are Equal by equals(), What Can You Say About Their hashCode()?
If two objects are equal by equals(), their hashCode() must be identical. This is one of the main rules in Java.
🟢 Junior Level
If two objects are equal by equals(), their hashCode() must be identical. This is one of the main rules in Java.
Why is this important? HashMap first searches for a bucket by hashCode(), and only inside the bucket does it check equals().
Why two levels: hashCode is fast (one operation), but imprecise (collisions). equals is precise (compares all fields), but slow. Fast filter first — then precise check. If hashes differ — HashMap won’t even call equals() and won’t find the element.
Example:
Person p1 = new Person("Ivan", 30);
Person p2 = new Person("Ivan", 30);
System.out.println(p1.equals(p2)); // true (if properly implemented)
System.out.println(p1.hashCode() == p2.hashCode()); // MUST be true!
Simple rule: Equal objects = same hashCode. Always.
🟡 Middle Level
Why is This Critical?
HashMap search works like this:
- Compute
hashCode()of the key - Determine bucket:
index = (n-1) & hash - Inside the bucket, search for element via
equals()
If hashCode differs for equal equals:
- HashMap goes to a different bucket
get()returnsnull, even though the element existsHashSetallows duplicatesremove()doesn’t delete the object
Consequences of Violation
| Problem | Description |
|---|---|
| Data loss | get() returns null for existing key |
| Duplicates | HashSet contains “identical” objects |
| Memory leak | Cannot remove object from the map |
Implementation in JDK
- String: identical strings → same hashCode (formula based on characters)
- Integer: hashCode = the int value itself
- Long: hashCode =
(int)(value ^ (value >>> 32)) - Object (default): identity hash code — a number that the JVM associates with the object on the first call to
System.identityHashCode(). It is NOT the memory address (though it may be derived from it) and does NOT change on GC moves.
How to Check
Static analyzers (SonarQube, SpotBugs) automatically find violations of this contract. IntelliJ IDEA warns when generating one method without the other.
When Same hashCode Is Not Enough
Same hashCode for equal objects is necessary but insufficient. hashCode must be consistent: if fields haven’t changed, repeated hashCode() calls must return the same number. If hashCode depends on current time or random values — the contract is violated.
🔴 Senior Level
Formal Requirement
From the Object.hashCode() Javadoc:
If two objects are equal according to the
equals(Object)method, then calling thehashCodemethod on each of the two objects must produce the same integer result.
This is an obligation, not a recommendation. Violating it breaks the entire Java Collections Framework.
Internal Mechanics of Violation
On map.put(objA, "value"):
hashA = hash(objA.hashCode()) = 100 → bucket 100
On map.get(objB) where objB.equals(objA) == true:
hashB = hash(objB.hashCode()) = 200 → bucket 200 → empty → null
The element is physically in bucket 100, but HashMap searches in bucket 200.
Heisenbug Effect
The error may not manifest if:
- Objects aren’t used in hash collections
- Different hashCodes accidentally landed in the same bucket (index matched via
(n-1) & hash) - On the next resize, indices change, and the code crashes
This creates a non-deterministic bug that’s hard to reproduce.
Memory and GC Impact
“Lost” elements in HashMap continue to occupy memory, but are unreachable for removal. With mass violation — memory leak in Old Gen.
Record and Lombok
// Record (Java 14+) — contract automatically satisfied
public record Key(String id) {}
// Lombok — both methods generated from the same fields
@EqualsAndHashCode
class Key { String id; }
Production Diagnostics
If in production get() returns null for an “existing” key:
- Check if both methods are overridden
- Check if mutable key fields have changed
- Check if symmetry is violated in inheritance
🎯 Interview Cheat Sheet
Must know:
- If equals = true, hashCode MUST be the same — obligation from Javadoc
- HashMap searches for bucket by hashCode, inside — by equals; different hashCodes = element not found
- Violation = data loss in Map, duplicates in Set, inability to remove
- Heisenbug effect: error may not manifest in small tests but crash in production
- Object.hashCode() by default = identity hash code, NOT memory address
- Identity hash code doesn’t change on GC relocation
Common follow-up questions:
- Can hashCode collide by accident for different equals? — yes, this is a collision, a normal situation
- Why is the error non-deterministic? — different hashCodes may accidentally land in the same bucket on a small map
- What is identity hash code? — a number the JVM associates with an object on the first call to System.identityHashCode()
- How to prevent? — use Records, Lombok @EqualsAndHashCode, IDE generation
Red flags (DO NOT say):
- “hashCode must only match for equal objects” — it CAN match for unequal ones too (collision)
- “This is a rare error” — one of the most insidious bugs: passes tests, crashes in production
- “Can be ignored if objects aren’t in Map” — yes, but it’s a fragile decision
Related topics:
- [[07. What is the equals() and hashCode() Contract]]
- [[09. If Two Objects Have the Same hashCode(), Are They Necessarily Equal by equals()]]
- [[10. What Happens If You Override equals() But Not hashCode()]]