<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) is true.
  • Symmetric: x.equals(y) iff y.equals(x).
  • Transitive: if x.equals(y) and y.equals(z), then x.equals(z).
  • Consistent: repeated calls return the same result as long as the objects don't change.
  • x.equals(null) is false.
  • If a.equals(b) then a.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 equals in 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 equals without hashCode β€” 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 a HashMap.
  • Using == to compare objects β€” reference identity, not equality. Use .equals().

Related

Pillar: Java methods. See also records, HashMap.