Language selector

How not to use (pattern matching with) instanceof

Could doesn’t mean should (and sometimes you shouldn’t)

In the chase for non-trivial examples to illustrate possible usage of JEP 305 (described by me here), some people might have gone too far, I guess. In particular into the more challenging area of the equals() method, present in all Java objects. Let’s do something, which makes some of my fellow developers truly shocked: let’s RTFM of the Object.equals() method. We can read there which conditions must be satisfied by a correct implementation of the equals() method. One of them is symmetric: having any two objects x and y, calling x.equals(y) and y.equals(x) must have the same result. Both have to be true or both have to be false. If one of them is true and the other is false, then the equals() method hasn’t been implemented correctly. Deal with that.

One of the myths of the programming world is that “inheritance is BAD”. (Having enough time I talk about this in CONTEXTVS, STVLTE!)

Inheritance and type hierarchies aren’t bad per se. I really can’t tell how someone can never ever use the inheritance and call it ‘object oriented programming’. What is bad is the clueless usage of the inheritance when it shouldn’t be used, because e.g. it violates the Liskov Substitution Principle. Add mindless usage of the JEP 305 to that, and we have a recipe for a disaster.

All right, what do you mean?

Let’s take a look at this code (an excerpt of this one):

 1class Point {
 2	public final int x;
 3	public final int y;
 4
 5	@Override
 6	public boolean equals(Object o) {
 7		if (this == o) return true;
 8		if (o instanceof Point that) {
 9			return this.x == that.x && this.y == that.y;
10		}
11		return false;
12	}
13	// ...
14}

It looks innocent at the first glance, right? Can we push that? Few weeks pass, and an innocent Junior comes in, who ‘only needs to implement Point, but in 3D, you see’:

 1class Point3D extends Point {
 2	public final int z;
 3
 4	@Override
 5	public boolean equals(Object o) {
 6		if (this == o) return true;
 7		if (o instanceof Point3D that) {
 8			if (!super.equals(o))
 9				return false;
10			return z == that.z;
11		}
12		return false;
13	}
14    //...
15}

It may look innocent at the first glance too… However, running a simple check of correctness of the equals() method:

1var point = new Point (1,1);
2var point3D = new Point3D(1,1,1);
3System.out.println(point.equals(point3D));
4System.out.println(point3D.equals(point));

yields:

true
false

The conclusion is simple: the implementation of the equals() methods is not symmetric. Oh my, but why, oh my? Well, JEP 305 is IMHO quite like any other technology: it should be used correctly, not misused, like in this very case. The mistake is in line 8. ‘But hey, it’s just using pattern matching with instanceof, it was made for this purpose, don’t you know?’ Well, I don’t think so. Point3D is not “ah, you see, just an ordinary point, only one more axis” and it’s incorrect to compare in equals everything that is a point. Yeap, the instanceof yields true not only for objects of exactly the same type, but also for every type, which inherits. Yeap, I know, it’s truism. But boy, I’ve seen too much code to know that this truism isn’t as widely known as it should be…

Loosen your tie, please

In real life (not tutorial one) we need to be pragmatic. Sometimes implementing equals() using instanceof and JEP 305 isn’t bad, if we all in the team are aware of the consequences, we will never inherit from this type, and we need to check identity this way, because some frameworks spawn inheriting proxy classes, e.g. from our entities.

So let me ask you this, please: when you see somewhere in a code (production or blog) instanceof inside the equals() method, be aware. And don’t copy & paste random code from the Internet. Context, stupid!

Language selector