Overriding equals & hashCode
Overriding equals & hashCode
In lesson 3 you learned how to override methods so a subclass can replace a parent's behaviour. Two methods that almost every real-world class eventually needs to override are equals and hashCode, both inherited from Object. Understanding why you need to override them — and the rules you must follow when you do — is essential Java knowledge.
Reference equality vs value equality
The == operator in Java checks reference equality: are these two variables pointing at the exact same object in memory? This is almost never what you want when comparing two objects that represent the same concept.
The equals method, when properly overridden, expresses value equality: do these two objects represent the same thing, even if they are different instances?
The default equals from Object
If you do not override equals, Java uses the default from Object, which simply does ==. Two distinct instances are therefore never equal, no matter how identical their fields are.
This is rarely the right answer for domain objects. Two points at (3, 4) should be considered equal.
Overriding equals correctly
The equals contract (from the Java documentation) requires five properties: reflexive (x.equals(x) is always true), symmetric (x.equals(y) implies y.equals(x)), transitive, consistent (repeated calls return the same result), and x.equals(null) always returns false.
getClass() instead of instanceof? Using instanceof can break symmetry when subclasses are involved: p1.equals(coloredPoint) might be true while coloredPoint.equals(p1) is false. getClass() keeps both directions consistent. For value-based records and sealed classes, instanceof is fine; for regular inheritance hierarchies, prefer getClass().
Why you must also override hashCode
Java's collections — HashMap, HashSet, Hashtable — rely on hashCode to place and find objects in buckets. The rule is strict:
- If
a.equals(b)istrue, thena.hashCode()must equalb.hashCode(). - If
a.hashCode() != b.hashCode(), thena.equals(b)must befalse(they cannot be equal). - Two objects that are not equal are allowed to have the same hash code (a collision), but fewer collisions means better performance.
If you override equals without overriding hashCode, equal objects can have different hash codes, and a HashSet will treat two logically identical objects as separate entries.
Overriding hashCode
A straightforward and collision-resistant approach uses Objects.hash(), which computes a combined hash of all the fields used in equals:
With both methods in place:
A complete example
equals, hashCode, and toString automatically. record Point(int x, int y) {} gives you all three for free. For plain classes you still write them by hand or let your IDE generate them.
HashSet, then change a field that is part of its hash, the set can no longer find it — the object is lost in the wrong bucket. Make the fields used in equals and hashCode immutable (or at least stable) whenever possible.
Summary
==checks reference equality;equalschecks value equality.- The default
equalsfromObjectis just==— override it for domain objects. - Follow the five-property
equalscontract: reflexive, symmetric, transitive, consistent, null-safe. - Always override
hashCodewhen you overrideequals— they must agree. - Use
Objects.hash(field1, field2, ...)for a clean, reliablehashCodeimplementation.