Type matching in Java equals - getClass() vs. instanceof
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 thePoint
class to aColorPoint
class by adding an extra color field to it. The overridden equals inColorPoint
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 subtypeColorPoint
(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 Shape
s 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 isCentre
⇿ Point
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.