Memra

equals, hashCode & toString done right

The equals contract, the hashCode contract, why they must change together, and a correct implementation.

Why the defaults are not enough

As you saw in Module 5, Object.equals compares identity and Object.hashCode returns a memory-derived number. For value objects — objects where two instances with the same data should be considered equal — you must override both.

The equals contract

A correct equals must satisfy five properties for any non-null references x, y, z:

  1. Reflexive: x.equals(x) is true.
  2. Symmetric: if x.equals(y) then y.equals(x).
  3. Transitive: if x.equals(y) and y.equals(z) then x.equals(z).
  4. Consistent: repeated calls with unchanged objects return the same result.
  5. Null-safe: x.equals(null) is always false — never NullPointerException.

The hashCode contract

- Objects that are equals must have the same hashCode. - Objects with the same hashCode need not be equals (collision is allowed).

Violating this contract causes HashSet to store duplicates and HashMap to lose entries silently.

A correct implementation

import java.util.Objects;

public class Point {
    private final int x, y;

    Point(int x, int y) { this.x = x; this.y = y; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;                  // same reference
        if (!(o instanceof Point p)) return false;   // null-safe + type check
        return x == p.x && y == p.y;
    }

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

    @Override
    public String toString() {
        return "Point(" + x + ", " + y + ")";
    }
}

Objects.hash() combines multiple fields correctly; it handles nulls safely for reference-type fields.

NORMAL ~/memra/learn/java-from-zero/equals-hashcode-tostring utf-8 LF