You are viewing a plain text version of this content. The canonical link for it is here.
Posted to user@struts.apache.org by Tamas Szabo <sz...@gmail.com> on 2006/03/16 16:54:22 UTC

[OT] inheritance and equals

Hi,

It's almost Friday here so I thought I could throw in an OT, hope you don't
mind ...

I'm sure that most of you already read the Effective Java of Joshua Bloch.
In the item discussing the equals method he talks about the imposibility to
write a valid equals method for a subclass that adds a new aspect to the
superclass. His example:

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

}

and the subclass which adds the notion of a color:

public class ColorPoint extends Point {
  private Color color;

  public ColorPoint(int x, int y, Color color) {
    super(x, y);
    this.color = color;
  }

   ... // Remainder omitted
}

We have to implement the equals method of ColorPoint but we must respect its
contract.
The trickier points from this contract are transitivity (if a.equals(b) and
b.equals(c) then a.equals(c))and symmetry (if x.equals(y) then y.equals(x)).

The first try is something like this and it breaks symmetry: (this is the
implementation I usually see :-))

//Broken - violates symmetry!
public boolean equals(Object o) {
  if (!(o instanceof ColorPoint))
    return false;
  ColorPoint cp = (ColorPoint)o;
  return super.equals(o) && cp.color == color;
}

For:
Point p = new Point(1, 2);
ColorPoint cp = new ColorPoint(1, 2, Color.RED);

p.equals(cp) is true but cp.equals(p) is false.

The second try is to handle Points specially in ColorPoint's equals, but it
breaks transitivity:

//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;
}

For:
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);

p1.equals(p2) is true and p2.equals(p3) is true
but p1.equals(p3) is false.


So my question is how do you solve this problem?
The book says that a workaround is to use composition instead of
inheritance, which in general is a good idea, but in my oppinion it doesn't
solve the problem at all.
If you want a Point to be equal to ColorPoints of any color but with the
same coordinates the provided workaround isn't correct.
Here it is:

// Adds an aspect without violating the equals contract
public class ColorPoint {
  private Point point;
  private Color color;

  public ColorPoint(int x, int y, Color color) {
    point = new Point(x, y);
    this.color = color;
  }

  /**
   * Returns the point-view of this color point.
   */
  public Point asPoint() {
    return point;
  }

  public boolean equals(Object o) {
    if (!(o instanceof ColorPoint))
      return false;
    ColorPoint cp = (ColorPoint)o;
    return cp.point.equals(point) && cp.color.equals(color);
  }
  ... // Remainder omitted
}

Does anyone has a valid workaround or you just accept that it isn't possible
for a superclass to be equal with all of its subclasses not considering the
aspect added by the subclass?

Another question is how do you usually add an aspect to an existing class?
Composition or inheritance?


Thanks and sorry for the long post

Tamas