In Effective Java, Item 7 is "Obey the general contract when overriding
equals
". There is one sentence "There is simply no way to extend an instantiable class and add an aspect while preserving the equals contract." Josh uses the following classes to illustrate
Transitivity. The following text is excerpted from the book.
The
Point class.
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Point)o;
return p.x == x && p.y == y;
}
... // Remainder omitted
}
The
ColorPoint class.
public class ColorPoint extends Point {
private Color color;
public ColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
//Broken - violates transitivity.
public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
// If o is a normal Point, do a color-blind
// comparison
if (!(o instanceof ColorPoint))
return o.equals(this);
// o is a ColorPoint; do a full comparison
ColorPoint cp = (ColorPoint)o;
return super.equals(o) && cp.color == color;
}
... // Remainder omitted
}
This approach does provide symmetry, but at the expense of transitivity:
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
At this point,
p1.equals(p2) and
p2.equals(p3) return
true, while
p1.equals(p3) returns
false, a clear violation of transitivity. The first two comparisons are “color-blind,” while the third takes color into account.
So what's the solution? It turns out that this is a fundamental problem of equivalence relations in object-oriented languages.
There is simply no way to extend an instantiable class and add an aspect while preserving the equals contract.Then Josh gives a a workaround where
ColorPoint does not extend
Point. I thinks that we can allow ColorPoint to extend Point and preserve the equals contract. Here is my solution.
The
Point class.
public class Point {
private final int x;
private final int y;
public Point( int x, int y ) {
this.x = x;
this.y = y;
}
public boolean equals( Object o ) {
if( this == o )
return true;
if( !(this.getClass() == o.getClass()) )
return false;
Point p = (Point) o;
return p.x == x && p.y == y;
}
... // Remainder omitted
}
The
ColorPont class.
public class ColorPoint extends Point {
private Color color;
public ColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
public boolean equals( Object o ) {
if( this == o )
return true;
if( o == null )
return false;
if( !(this.getClass() == o.getClass()) )
return false;
ColorPoint cp = (ColorPoint) o;
return super.equals( o ) && cp.color == color;
}
... // Remainder omitted
}
First,
equals method use the == operator to check if the argument is a reference to this object. Secondly, it check whether the argument and this object are of the same type. I have tested this piece of code. It works. But I think that there may be some situations I have not taken into account.
Any comments are welcome.