W3docs

Java equals() and hashCode()

Correctly override equals() and hashCode() in Java classes to support collections and value-based equality.

equals and hashCode are the two Object methods that hash-based collections — HashMap, HashSet, LinkedHashMap, anything backed by hashing — silently rely on. Get them right and your objects behave as values: set.contains(point) finds the point regardless of which new Point(3, 4) instance you pass. Get them wrong and you get duplicates in sets, missing keys in maps, and bugs that only show up under load.

The default inherited from Object compares identity: two references are equal only when they point to the same object. That's fine for things like database connections, where each instance is a distinct resource. For value-like classes — money, points, names, dates — you almost always want content equality instead, and that means overriding both methods together.

The contract

equals must satisfy four rules:

  • Reflexivex.equals(x) is true.
  • Symmetricx.equals(y) iff y.equals(x).
  • Transitive — if x.equals(y) and y.equals(z), then x.equals(z).
  • Consistent — repeated calls with unchanged fields give the same answer.

Plus: x.equals(null) must return false, never throw.

hashCode has one rule that ties it to equals:

  • Equal objects must have equal hash codes. Unequal objects may share a hash code (collisions are allowed, just bad for performance).

That single rule is why you can't override one without the other. If a.equals(b) but a.hashCode() != b.hashCode(), HashSet puts them in different buckets, contains finds the wrong one, and you have a ghost duplicate.

Anatomy of a correct equals

A working equals follows a standard shape:

public final class Point {
  private final int x;
  private final int y;
  public Point(int x, int y) { this.x = x; this.y = y; }

  @Override
  public boolean equals(Object o) {
    if (this == o)                      return true;
    if (!(o instanceof Point p))        return false;
    return x == p.x && y == p.y;
  }

  @Override
  public int hashCode() {
    return Objects.hash(x, y);
  }
}

Step by step:

  • Identity short-circuit. this == o catches the common case fast.
  • Type check with binding. instanceof Point p rejects nulls and wrong types in one expression and binds the narrowed reference.
  • Field comparison. Use == for primitives, Objects.equals(a, b) for nullable references, Float.compare / Double.compare for floats.

Objects.hash(...) builds a hash from a list of fields. It's slightly slower than hand-written XOR/multiply code, but it's correct and unambiguous.

getClass or instanceof?

Two camps:

  • instanceof allows a subclass instance to be equal to a parent instance if the comparison field set is the same. Slightly more flexible.
  • getClass() insists on the exact runtime class. Easier to keep symmetric across hierarchies, but breaks substitutability.

For most value-style classes, the simplest path is to make the class final and use instanceof. Without final, mixing the two styles across a hierarchy is where most equality bugs live. Records sidestep the decision entirely — they're implicitly final and the generated equals uses an exact-type check.

Floating-point fields

Don't use == on double or float fields — the value +0.0 equals -0.0 under ==, but Double.compare treats them differently, and NaN == NaN is false. Double.compare(a, b) == 0 and Float.compare give the consistent answer required by the contract.

Arrays

Object.equals on an array compares references, not contents. Use Arrays.equals(a, b) for one-dimensional arrays, Arrays.deepEquals for multidimensional. Similarly, use Arrays.hashCode / Arrays.deepHashCode in hashCode.

Mutability is hostile to hash-based collections

If you mutate a field that's part of equals/hashCode after putting the object into a HashSet, the bucket the set placed it in stops matching the new hash — and the object becomes unreachable through contains. The safest rule: fields used in equals should be final. If that's not possible, never put the object into a hash-based collection.

Don't write either by hand for plain data classes

If the class is a pure data carrier, prefer a record — the compiler generates correct equals and hashCode for you, and the two will always stay in sync as fields change. If you can't use a record, your IDE's "generate equals/hashCode" command is the next best thing.

A worked example

java— editable, runs on the server

What's next

equals lets your objects compare themselves; toString lets them describe themselves. The next chapter is about overriding toString to produce output that's actually useful in logs, error messages, and debuggers. Continue to Java toString method.

Practice

Practice

Why is it a bug to override `equals` without also overriding `hashCode`?