The Contract of equals

If you need logical equality (instead of object identity), override equals(), and obey the contract when overriding it (think of value classes).

Given non-null references x, y, and z, the contract includes:

  • Reflexive: x.equals(x).
  • Symmetric: x.equals(y) == y.equals(x).
  • Transitive: x.equals(y) && y.equals(z) == x.equals(z).
  • Consistent: Multiple calls of x.equals(y) should yield the same result.
  • Non-Nullity: x.equals(null) == false.

And you should always also override hashCode() when overriding equals().

Nevertheless, it is not always possible to obey both the contract and object-oriented principles while extending a class.

public class Point {
  private final int x;
  private final int y;

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

  ...
}

public class ColorPoint extend Point {
  private final Color color;

  // Violate symmetry.
  @Override
  public boolean equals(Object o) {
    if (!(o instanceof ColorPoint)) {
      return false;
    }
    return super.equals(o) && color == ((ColorPoint) o).color;
  }

  // Violate Liskov substitution principle.
  @Override
  public boolean equals(Object o) {
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    Point p = (Point) o;
    return x == p.x && y == p.y;
  }

  ...
}

(So think twice when you want to make a “trivial” extension; use composition instead.)

Creative Commons License
This blog by Che-Liang Chiou is licensed under a Creative Commons Attribution 4.0 International License.