All 15 equals(), hashCode() & toString() Contracts questions
More in equals(), hashCode() & toString() Contracts
Advertisement
Java › equals(), hashCode() & toString() Contracts

Why must hashCode() be overridden whenever equals() is overridden, and what specific, hard-to-diagnose bugs occur in a HashMap when this contract is violated (equals() overridden but hashCode() left as default)?

Advanced

Answer

The equals()/hashCode() contract requires that equal objects (per equals()) MUST produce the same hashCode() value -- if you override equals() to compare field values but leave hashCode() as Object's default identity-based implementation, two logically-equal instances will very likely produce different hash codes, causing a HashMap to place them in different buckets; the practical symptom is that map.get(key) or map.containsKey(key) can return null/false for a key that IS logically present (constructed as a separate but equal instance), since the lookup uses hashCode() first to locate the right bucket before ever calling equals() to confirm a match -- a notoriously confusing bug because the map appears to have silently "lost" or "rejected" data that was, in fact, correctly inserted.
public class BadKey {
    private final String id;
    public BadKey(String id) { this.id = id; }

    @Override
    public boolean equals(Object o) {  // overridden
        return o instanceof BadKey k && id.equals(k.id);
    }
    // hashCode() NOT overridden -- still uses Object's identity-based default!
}

Map<BadKey, String> map = new HashMap<>();
map.put(new BadKey("x"), "value");
System.out.println(map.get(new BadKey("x")));  // prints null! Different hashCode() despite equals() returning true
Real-world example A caching layer using a custom composite key class that overrode equals() but forgot hashCode() experienced intermittent cache misses that looked exactly like cache entries randomly disappearing, actually caused by every lookup using a fresh key instance landing in a different bucket than the one used during insertion, undetectable without understanding this specific contract violation.

Common follow-ups: Why does a HashMap check hashCode() before ever calling equals(), rather than just calling equals() directly against every entry?;How would you write a unit test specifically to catch an equals()/hashCode() contract violation like this?