Question

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?

Was it helpful?

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?

OTHER TIPS

LSP is not required by the equals contract.

Perhaps this question is the result of the unnecessary inclusion of LSP on the topic of equals implementations. The standard eclipse generated equals implementation already utilizes super.equals (taking advantage of inheritance) and is much more correct and less complicated than the suggested solution. However, it does not meet the criteria of LSP, which is ok according to the documentation.

I want to know the what is LSP, so I come across one good article. As article says The Liskov Substution Principle does not hold in Java

LSP defines a weak constraint on the set of properties, but then claims that for any property it can be shown that the program behaviour is the same. The only way this can ever be validated is if the original objects in the system are so tightly defined by their specification that they admit no other implementation. For example, the authors of the above paper claim that given a set of properties about a method, they can claim 100% behavioural compatibility with another method that has the same contract – and this is NOT TRUE. In essence, I can claim that the contract of the toString() method in Java is very weak – “Returns a string representation of the object”.

Why is the LiskovSubstitutionPrinciple important?

  1. Because if not, then class hierarchies would be a mess. Mess being that whenever a subclass instance was passed as parameter to any method, strange behavior would occur.
  2. Because if not, unit tests for the superclass would never succeed for the subclass.

Refer link, may clear your doubt.

There is no difficulty with having an instantiable class add a value component while abiding by both the equals class and the LSP, if the contract of the base class would allow derived classes to do so, and if the equals method of the base class is coded in such a way as to either

  1. Check the exact type of anything with which it is compared, and report false if the exact types don't match.

  2. Define a method which it uses as part of the equality-check process, and which a derived class can override as needed.

Using approach #1, any derived class may define equals to consider new fields without creating any difficulty, since the only things to which a derived-class instance could possibly be considered equal would be other instances of that same derived class.

Using approach #2 is a bit trickier, but a simple approach might be to override equals something to call a protected equals2 method like this:

@override boolean equals(Object other)
{
  if (!(other instanceof MyType)) return false;
  MyType otherAsMyType = (MyType)other;
  return this.equals1(OtherAsMyType) && OtherAsMyType.equals1(this) &&
         this.equals2(OtherAsMyType) && OtherAsMyType.equals2(this);   
}

The equals1 method of the derived type would return false when passed something that wasn't derived from it, even if that other object knew nothing about the derived part; splitting the comparison into the different parts will ensure that if a type can quickly determine that an object of the other type cannot possibly be equal to it, the code in the first class won't waste time checking parts that seemingly could be equal.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top