Question 8 · Section 10

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.

Language versions: English Russian Ukrainian

🟢 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:

  1. Compute hashCode() of the key
  2. Determine bucket: index = (n-1) & hash
  3. Inside the bucket, search for element via equals()

If hashCode differs for equal equals:

  • HashMap goes to a different bucket
  • get() returns null, even though the element exists
  • HashSet allows duplicates
  • remove() 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 the hashCode method 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:

  1. Check if both methods are overridden
  2. Check if mutable key fields have changed
  3. 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()]]