🧬 Inheritance & Polymorphism · Intermediate

hashCode() contract in Java

Equal objects must have equal hash codes; override both together.

🧩 The mysteryYou add a Point to a HashSet, then ask for an equal Point. The set says "never heard of it". Your equals is perfect. So what's missing?

Buckets and coat hooks

Hash collections work like a coat check. **hashCode() picks the hook (bucket); only then does equals compare the items hanging there. If two equal objects get different hash codes, the set looks on the wrong hook** and equals is never even called.

The golden rule

**If a.equals(b), then a.hashCode() == b.hashCode().** So whenever you override equals, override hashCode too, using the same fields. Objects.hash(...) makes that easy.

@Override
public int hashCode() {
    return Objects.hash(name, age);
}
⚠️ The trap

equals without hashCode

Override only equals and you inherit Object's identity-based hashCode. Two equal Pts almost always get different hash codes, so set.contains(new Pt(1)) returns false — even right after set.add(new Pt(1)).

🔮 Predict it

A famous collision

What does this print?

int h1 = "Aa".hashCode(), h2 = "BB".hashCode();
System.out.println(h1 == h2);
System.out.println("Aa".equals("BB"));
  1. true false
  2. false false
  3. true true
Show the answer

true, then false. Same hash code does not mean equal — different objects can collide on the same int. That's fine: equals settles the question.

🔮 Predict it

Both overridden

Pt overrides both equals and hashCode. What prints?

// Pt overrides equals AND hashCode,
// both based on its x field
Set<Pt> s = new HashSet<>();
s.add(new Pt(1));
s.add(new Pt(1));
System.out.println(s.size());
System.out.println(s.contains(new Pt(1)));
  1. 2 false
  2. 1 true
  3. 2 true
Show the answer

1, then true. The second add finds an equal element on the same hook and is ignored, and a fresh Pt(1) is found.

Choosing a hashCode

✗ Legal but bad
public int hashCode() {
    return 42;
}

Correct but every object lands in one bucket: lookups crawl. super.hashCode() (identity) or random values break the contract outright.

✓ Best
public int hashCode() {
    return Objects.hash(name, age);
}

Same fields as equals: equal objects match, others spread out.

💼 In the real world

In real projects

A sneaky production bug: using an object as a HashMap key, then mutating a field that feeds hashCode. The entry is now on the wrong hook — get returns null and the entry is effectively lost. Keep keys immutable.

Key takeaways

  1. a.equals(b) means a.hashCode() == b.hashCode()
  2. Same hash code does NOT imply equal
  3. Override equals and hashCode together
  4. Objects.hash(f1, f2) is an easy correct implementation
🤯 Did you know?

String's hash formula is part of its Javadoc: s[0]*31^(n-1) + ... + s[n-1]. That's why "Aa" and "BB" (both 2112) collide on every JVM in the world.

Practice questions

Pt overrides equals (comparing x) but not hashCode. After set.add(new Pt(1)), set.contains(new Pt(1)) returns false. Why?

  1. The two equal objects have different identity-based hash codes, so the set searches the wrong bucket
  2. HashSet only uses ==, never equals
  3. contains() requires Pt to implement Comparable
  4. The first Pt was garbage-collected
Check your answer

The two equal objects have different identity-based hash codes, so the set searches the wrong bucket. Without hashCode, Object's identity-based version is used, so equal Pts almost always get different hashes and equals is never even called.

Pt overrides both equals and hashCode using its x field. What does this print?

var a = new Pt(1);
var b = new Pt(1);
Set<Pt> set = new HashSet<>();
set.add(a);
set.add(b);
System.out.println(set.size());
System.out.println(set.contains(new Pt(1)));
  1. 2 false
  2. 1 true
  3. 2 true
  4. 1 false
Check your answer

1 true. With matching equals and hashCode, the second add finds an equal element in the same bucket and is ignored, and a fresh Pt(1) is found.

Next: should a Car *extend* Engine, or *have* one? Composition over inheritance.