<code>equals()</code> and <code>hashCode()</code> in Java
equals(Object) decides whether two objects are logically equal. hashCode() returns an int used to find objects in hash-based collections. They're a pair β breaking their contract silently breaks HashSet, HashMap and friends.
The contract
- Reflexive:
x.equals(x)istrue. - Symmetric:
x.equals(y)iffy.equals(x). - Transitive: if
x.equals(y)andy.equals(z), thenx.equals(z). - Consistent: repeated calls return the same result as long as the objects don't change.
x.equals(null)isfalse.- If
a.equals(b)thena.hashCode() == b.hashCode(). The reverse is not required.
A canonical implementation
public final class Point {
private final int x, y;
public Point(int x, int y) { this.x = x; this.y = y; }
@Override
public boolean equals(Object o) {
if (this == o) return true; // fast path
if (!(o instanceof Point p)) return false; // null + type check
return x == p.x && y == p.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y); // since Java 7
}
}
Records give you both for free
public record Point(int x, int y) {}
// equals and hashCode auto-generated from all components.
For any "data class", use a record. You inherit a correct, consistent implementation and can't break the contract by accident.
The collection bug
class Bad {
final int id;
Bad(int id) { this.id = id; }
public boolean equals(Object o) { return o instanceof Bad b && b.id == id; }
// no hashCode override β inherits Object's identity-based one
}
var set = new HashSet<Bad>();
set.add(new Bad(1));
set.contains(new Bad(1)); // false β different hashCode β different bucket
Rules for subclasses
If a superclass defines equals by a set of fields, a subclass that adds state cannot override equals in a way that breaks symmetry. Either:
- Keep
equalsin the parent and accept that subclass instances compare equal as long as the parent's fields match. - Use composition β include the parent as a field instead of extending it.
IDE generators and Lombok
IntelliJ and Eclipse generate a correct equals/hashCode from selected fields. Lombok's @EqualsAndHashCode does the same via annotation. Both are fine β still prefer records for new code.
Common mistakes
- Overriding
equalswithouthashCodeβ guaranteed collection bugs. - Wrong signature:
equals(MyClass o)is an overload, not an override. Always use@Override. - Mutable fields in
equalsβ if the object changes, its hash changes and it gets lost in aHashMap. - Using
==to compare objects β reference identity, not equality. Use.equals().
Related
Pillar: Java methods. See also records, HashMap.