Search code examples
javainheritanceequalseffective-java

A way to extend an instantiable class and add a value component while preserving the equals contract


Effective Java by Joshua Blotch states:

There is no way to extend an instantiable class and add a value component while preserving the equals contract, unless you are willing to forgo the benefits of object-oriented abstraction.

Effective Java gives some examples which either break symmetry, transitivity or Liskov substitution principle. I suppose you have read the item. I don't think I should post the whole item here. However I found a solution which obeys the contract:

The Point :

public class Point{
    private final int x;
    private final int y;
    public Point(int x, int y){
        this.x = x;
        this.y = y;
    }
    protected boolean equalsImpl(Point p){
        return p.x == x && p.y == y;
    }
    @Override
    public final boolean equals(Object o) {
        if(!(o instanceof Point))
            return false;
        Point p = (Point)o;
        return equalsImpl(p) && p.equalsImpl(this);
    }
    // Remainder omitted ...
}

The ColorPoint :

public class ColorPoint extends Point{
    private java.awt.Color color;
    public ColorPoint(int x, int y, java.awt.Color color){
        super(x, y);
        this.color = color;
    }
    @Override
    protected boolean equalsImpl(Point p){
        return (p instanceof ColorPoint) &&
                super.equalsImpl(p) &&
                ((ColorPoint)p).color == color;
    }
    // Remainder omitted ...
}

I know composition should be used in this case, but I wonder whether my solution really obeys the equals contract. If yes, is what Effective Java states wrong?


Solution

  • With this implementation no ColorPoint is equal to a Point, so it breaks the Liskov substitution principle. It does honour the equals contract though.

    Update: Whether LSP is broken or not depends on the contracts that the Point class has. If you have a contract that says "two points that have the same X and Y coordinates are equal" or "you can use a HashSet to eliminate points with duplicate coordinates", it is broken. If no such contract exists LSP is not broken.

    On the other hand, if no ColorPoint is equal to a Point, does it make sense that a ColorPoint is-a Point?