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?
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
?