When you override equals() to define logical equality, you must also override hashCode() — because hash-based collections (HashMap, ) rely on the contract that . Breaking this leads to subtle, hard-to-find bugs.
HashSet// by default, equals() compares IDENTITY (same object?), hashCode() is based on memory address
Person p1 = new Person("Ann", 30);
Person p2 = new Person("Ann", 30);
p1.equals(p2); // false by default — different objects, even with same data
Without overriding, two objects with identical content are "not equal" — usually not what you want for value objects.
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person p = (Person) o;
return age == p.age && Objects.equals(name, p.name); // compare by CONTENT
}
@Override
public int hashCode() {
return Objects.hash(name, age); // MUST be consistent with equals()
}
The contract: if a.equals(b) is true, then a.hashCode() == b.hashCode() MUST be true.
Person p1 = new Person("Ann", 30);
Person p2 = new Person("Ann", 30); // equal by our equals()
Set<Person> set = new HashSet<>();
set.add(p1);
set.contains(p2); // ❌ likely FALSE — even though p1.equals(p2)!
Why? HashMap/HashSet first use hashCode() to find the right bucket, then equals() within it. With the default (identity-based) hashCode(), p1 and p2 land in different buckets, so contains never even compares them — the set thinks they're different. This produces baffling bugs: duplicates in a Set, failed map lookups, etc.
1. equal objects → equal hash codes (REQUIRED for correctness)
2. unequal objects MAY have the same hash (collisions are allowed)
3. hashCode() must be consistent (same object → same code, unchanged)
4. equals() must be reflexive, symmetric, transitive, consistent
record Person(String name, int age) {} // records auto-generate equals/hashCode/toString!
Java records (and IDE generation / Lombok) produce correct, consistent equals/hashCode for you.
The equals/hashCode contract is one of Java's most important — and most commonly violated — rules.
Overriding equals() without hashCode() breaks hash-based collections in ways that are hard to debug: objects you consider equal get "lost" in HashMap/HashSet (failed lookups, phantom duplicates) because they hash to different buckets.
Since these collections are ubiquitous, every value-type class used as a key or set element must override both consistently.
Understanding why (the bucket-then-equals lookup mechanism) — and using records or generated code to get it right — is essential for correct behavior and a classic interview topic that reveals real understanding of how Java collections work.