Question 7 · Section 10

What is the equals() and hashCode() Contract?

The equals() method must behave as the mathematical notion of 'equality':

Language versions: English Russian Ukrainian

🟢 Junior Level

The equals() and hashCode() contract is a set of rules that equals() and hashCode() methods in Java must follow. If violated, collections like HashMap and HashSet will malfunction.

Two simple rules:

  1. If two objects are equal by equals(), their hashCode() must be the same
  2. If hashCode() is the same — objects are not necessarily equal (this is called a collision)

Correct contract example:

public class Person {
    private String name;
    private int age;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;
        Person p = (Person) o;
        return age == p.age && name.equals(p.name);
    }

    @Override
    public int hashCode() {
        return 31 * name.hashCode() + age;
    }
}

Main rule: if an object will be used as a key in HashMap/HashSet — override both methods. Exception: if you INTENTIONALLY want identity behavior (e.g., for weak references).

🟡 Middle Level

The equals() Contract

The equals() method must behave as the mathematical notion of ‘equality’:

  • Reflexivity: an object is equal to itself: a.equals(a) = true
  • Symmetry: if A equals B, then B equals A
  • Transitivity: if A=B and B=C, then A=C
Property Description
Reflexivity x.equals(x) is always true
Symmetry If x.equals(y), then y.equals(x)
Transitivity If x.equals(y) and y.equals(z), then x.equals(z)
Consistency Repeated calls give the same result
Null comparison x.equals(null) is always false

The hashCode() Contract

  1. Consistency: value doesn’t change if fields from equals() haven’t changed
  2. Mandatory link: x.equals(y)x.hashCode() == y.hashCode()
  3. Non-mandatory link: same hashCode ≠ equal objects

How to Implement Correctly

Joshua Bloch’s algorithm:

int result = 17;
result = 31 * result + field1;
result = 31 * result + (field2 != null ? field2.hashCode() : 0);
return result;

Why 17 and 31: 17 — initial ‘prime seed’, so the hash of first fields isn’t zero. 31 — an odd prime: multiplication by 31 = left shift by 5 minus 1 (31 * i = (i << 5) - i), which is efficient on CPU. Integer overflow is not a bug — it’s normal: hash ALWAYS wrap-around, this is built into the math.

Common Mistakes

  1. Overriding equals, forgetting hashCode — duplicates in HashSet
  2. Using mutable fields — key “breaks” after modification
  3. Classic JDK bug: Timestamp extends Date. date.equals(timestamp) may return true, but timestamp.equals(date) — false, because Timestamp checks nanoseconds that Date doesn’t have. Symmetry is violated!

When NOT to Use Custom equals/hashCode

Don’t use equals/hashCode for objects with business identity that can change. For example, an entity with auto-generated ID: before saving id=null, after — id=42. Two objects with id=null are ‘equal’, but after saving — they’re not. This violates consistency in HashMap.

🔴 Senior Level

Internal Implementation in HashMap

HashMap uses both methods for O(1) search:

  1. hashCode() → finds bucket: index = (n-1) & hash(key)
  2. equals() → finds key inside the bucket

Consequences of violation:

  • Equal objects with different hashCodes land in different buckets → get() returns null
  • HashSet allows duplicates → business logic violation
  • remove() doesn’t find the object → memory leak

Symmetry Violation in Inheritance

class Point2D { int x, y; }
class Point3D extends Point2D { int z; }

If Point3D.equals() checks all 3 fields, but Point2D.equals() only 2:

  • new Point2D(1,1).equals(new Point3D(1,1,0))true (Point2D doesn’t see z)
  • new Point3D(1,1,0).equals(new Point2D(1,1))false (Point3D not instanceof Point3D)
  • Symmetry violation!

Solution: use getClass() instead of instanceof, or composition instead of inheritance.

Edge Cases

  1. URL.equals() — makes DNS requests, violates consistency on network failure
  2. Float.NaNFloat.NaN.equals(Float.NaN) = true, but Float.NaN == Float.NaN = false
  3. Arraysequals() compares references, not content; use Arrays.equals()

Performance Implications

  • Poor hashCode → collisions → HashMap degradation to O(log n) or O(n)
  • Heavy equals() (comparing large strings/byte arrays) → large constant in O(1)
  • Caching hashCode (as in String) — critical for performance

Java 14+: Records

public record Person(String name, int age) {}

Records automatically generate equals() and hashCode() based on all components, guaranteeing contract compliance.

Security

Contract violations can lead to DoS attacks via Hash Flooding. A quality hashCode() is not just correctness, but also security.


🎯 Interview Cheat Sheet

Must know:

  • equals() — reflexivity, symmetry, transitivity, consistency, null = false
  • hashCode() — if equals = true, hashCode MUST match
  • The reverse is false: same hashCode ≠ equal objects (collision)
  • Bloch’s algorithm: initial 17, multiplier 31 (=(i«5)-i), include all fields from equals
  • 31 — odd prime, JVM optimizes multiplication via shift
  • Integer overflow — normal, hash always wrap-around
  • Records (Java 14+) automatically generate both methods

Common follow-up questions:

  • What happens if you override only equals? — equal objects land in different buckets, get returns null
  • Why 31, not 17 or 37? — 31 = (i«5)-i, one operation instead of multiplication
  • What is symmetry violation? — A.equals(B) = true, but B.equals(A) = false (classic bug: Timestamp vs Date)
  • When NOT to override? — when identity semantics is needed (comparison by reference, not content)

Red flags (DO NOT say):

  • “hashCode must be unique” — no, collisions are allowed
  • “You can override only one method” — no, always the pair
  • “URL is a good key” — no, URL.equals() makes DNS requests, violates consistency

Related topics:

  • [[03. How HashMap Determines Which Bucket to Put an Element In]]
  • [[08. If Two Objects Are Equal by equals(), What Can You Say About Their hashCode()]]
  • [[09. If Two Objects Have the Same hashCode(), Are They Necessarily Equal by equals()]]
  • [[10. What Happens If You Override equals() But Not hashCode()]]