All 15 equals(), hashCode() & toString() Contracts questions
More in equals(), hashCode() & toString() Contracts
Advertisement
Back to results for “OOP & Classes”
Java › equals(), hashCode() & toString() Contracts

How does the getClass() versus instanceof choice in an equals() implementation affect whether subclasses can be considered equal to their superclass instances, and what's the Liskov Substitution Principle tension involved?

Advanced

Answer

Using getClass() == getClass() comparison in equals() strictly requires both objects to be the exact same runtime class (a subclass instance is never equal to a superclass instance, even if all inherited fields match), which preserves the transitivity contract robustly across inheritance but can feel overly restrictive; using instanceof instead allows a subclass instance to potentially be equal to a superclass instance, which feels more flexible but can easily break transitivity when a subclass adds new fields relevant to equality (a classic example: a ColorPoint subclass of Point where two ColorPoints with different colors but same coordinates might each separately equal a plain Point via instanceof-based comparison, yet not equal each other, violating transitivity) -- Josh Bloch's Effective Java ultimately recommends favoring composition over inheritance for value classes specifically to sidestep this tension entirely.
// getClass() approach: strict, safe, but a subclass can NEVER equal its superclass
public boolean equals(Object o) {
    if (o == null || getClass() != o.getClass()) return false;
    Point p = (Point) o;
    return x == p.x && y == p.y;
}

// instanceof approach: more flexible, but risks violating transitivity if subclasses add fields
public boolean equals(Object o) {
    if (!(o instanceof Point p)) return false;
    return x == p.x && y == p.y;  // ColorPoint could pass this using only inherited x/y!
}
Real-world example A geometry library's Point class initially uses instanceof-based equals(), and a later-added ColorPoint subclass introduces subtle transitivity-violating bugs in a Set<Point> containing a mix of Point and ColorPoint instances, resolved by switching to getClass()-based comparison (accepting the trade-off that a ColorPoint can never equal a plain Point) or by making Point final and using composition instead of inheritance for ColorPoint.

Common follow-ups: Why does Effective Java specifically recommend favoring composition over inheritance to avoid this equals() dilemma entirely?;What are the practical consequences of getClass()-based equals() when working with proxies or ORM-generated subclasses (like Hibernate entities)?