Type matching in Java equals - getClass() vs. instanceof

[2011-09-25]

I look at the equivalence comparison of objects in the Java language

There is an ongoing dispute on how type matching in the equals method should be implemented. A nice overview article can be found in Angelika Langers Secrets of equals() - Part 1 where you can find additional useful references. In short there are two possible ways to test the object’s type in the equals method:

if (getClass() != obj.getClass())

or

if (!(obj instanceof SomeClass))

The properties of the getClass() approach can be summarized as:

  • you can add fields into the subtypes and use them in subtype’s equals method. The notoriously popular example is that of subclassing the Point class to a ColorPoint class by adding an extra color field to it. The overridden equals in ColorPoint can match the point’s coordinates and also it’s color.
  • The drawback of this approach is that the supertype Point can never be equal to its subtype ColorPoint (a good example is in Josh Bloch’s book Effective java, chapter 3)

Simply put in the instanceof approach:

  • the subtype and supertype can be equal,

  • but equals is constrained to matching supertype fields only. The instanceof in supertype’s equals method does not reveal which type of the class hierarchy you are currently examining. As a result you only use supertype’s equals method and therefore you check only fields common to all types in the hierarchy. There is no way to add a field in a subtype that will be checked in the subtype’s overridden equals method without breaking the properties of equivalence relation.

At this point we see that one can argue for both approaches, but there is another aspect that makes a big difference. The former approach does not conform to the Liskov Substitution Principle - LSP while the latter does. For starters a good description of LSP is in the article from Robert C. Martin, the original Liskov paper is a bit tough to read. There are several formulations of LSP, I think about LSP as:

The subtype can’t break the contract between the supertype and method (function) where it is used.

Although the getClass() approach breaks LSP the debate around getClass() vs. instanceof still goes on.

On both projects I’ve worked so far (I am a junior dev) the getClass() approach was used for “better flexibility”. Although the LSP is a design guideline not a rigid OO theorem there are numerous simple examples where not following the LSP might cause problems. Consider a Shape class that checks whether a Point is the Shapes center:

class Shape {

    public boolean isCentre(Point p) {
        Point centre = getCentre();
        return centre.equals(p);
    }
}

If this method is called with a ColorPoint the result is false although the ColorPoint might have the shapes center coordinates. The ColorPoint violates the conditions of the isCentre method which was imposed by the isCentrePoint contract and thus breaks the LSP. This behaviour is a trap for a developers.

A safe way to do type matching that conforms to OO principles is by using the instanceof operator. It is of course true that systems can be also implemented with the getClass() approach, you just won’t be able to rely on the logical equivalence in the system.