equals Across a Class Hierarchy
Why adding a field in a subclass breaks equals symmetry, and the two honest ways out of it.
Open this lesson in the learning hubKey points
- equals must be symmetric: if
a.equals(b)is true thenb.equals(a)must be true as well. - Add a field in a subclass and an
instanceofbased equals loses that: the parent says yes, the child says no. - Then
containsanswers differently depending on which object you search with, because it calls equals on the argument. - Switching to
getClass() != o.getClass()restores symmetry, but no subclass instance can ever equal a parent one. - No version keeps both. Make the class
final, or hold the value in a field instead of extending it. - Records dodge the problem completely: they are final, so this kind of hierarchy cannot exist at all.
Example
import java.util.List;
public class Main {
static class Point {
final int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
@Override public boolean equals(Object o) {
if (!(o instanceof Point p)) return false; // any Point will do
return x == p.x && y == p.y;
}
@Override public int hashCode() { return 31 * x + y; }
@Override public String toString() { return "Point(" + x + "," + y + ")"; }
}
static final class ColourPoint extends Point {
final String colour;
ColourPoint(int x, int y, String colour) { super(x, y); this.colour = colour; }
@Override public boolean equals(Object o) {
if (!(o instanceof ColourPoint c)) return false; // stricter than the parent
return super.equals(o) && colour.equals(c.colour);
}
@Override public int hashCode() { return 31 * super.hashCode() + colour.hashCode(); }
@Override public String toString() { return "ColourPoint(" + x + "," + y + "," + colour + ")"; }
}
public static void main(String[] args) {
Point p = new Point(1, 2);
ColourPoint cp = new ColourPoint(1, 2, "red");
System.out.println("p.equals(cp) = " + p.equals(cp)); // true
System.out.println("cp.equals(p) = " + cp.equals(p)); // false: not symmetric
// contains calls argument.equals(element), so the answer flips with the order.
System.out.println("List.of(p).contains(cp) = " + List.of(p).contains(cp));
System.out.println("List.of(cp).contains(p) = " + List.of(cp).contains(p));
}
}
Value equality and inheritance do not mix: make it final, or use composition.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the OOP course, and every lesson in it is listed on the OOP contents page.