When a code contains the Java instanceof
operator, many people will raise their eyebrows and say it is a no-no. For example, in this other SO Q&A, the answer said:
Note that if you have to use that operator very often it is generally a hint that your design has some flaws. So in a well designed application you should have to use that operator as little as possible (of course there are exceptions to that general rule).
However, it does not further elaborate when the use of instanceof
is okay, and when it is not.
I put some thinking on this, and articlate the following guideline. I thought this may have been discussed somewhere on the Internet, but I could not find it. Hence this question and asking for your comment:
Using
instanceof
on an interface is okay; usinginstanceof
on an implementation is not okay
Here is an example on the "okay" case.
Example: A catalog of animals, some (but not all) of them can fly
Animal.java
public interface Animal {
String getName();
String makeNoise();
}
CanFly.java
public interface CanFly {
float getMaxInAirDistanceKm();
}
Cat.java
public class Cat implements Animal {
@Override
public String getName() {
return "Cat";
}
@Override
public String makeNoise() {
return "meow";
}
}
BaldEgale.java
public class BaldEagle implements Animal, CanFly {
@Override
public String getName() {
return "BaldEagle";
}
@Override
public String makeNoise() {
return "whistle";
}
@Override
public float getMaxInAirDistanceKm() {
return 50;
}
}
Catalog.java
import java.util.ArrayList;
import java.util.List;
public class Catalog {
private List<Animal> animals = new ArrayList<>();
public void putAnimal(Animal animal) {
animals.add(animal);
}
public void showList() {
animals.forEach(animal -> {
StringBuilder sb = new StringBuilder();
sb.append(animal.getName() + ": ");
sb.append(animal.makeNoise() + " ");
// this block exemplifies some processing that is
// specific to CanFly animals
if (animal instanceof CanFly) {
sb.append(String.format(" (can stay in air for %s km)",
((CanFly) animal).getMaxInAirDistanceKm()));
}
System.out.println(sb.toString());
});
}
public static void main(String[] args){
Catalog catalog = new Catalog();
Cat cat = new Cat();
BaldEagle baldEagle = new BaldEagle();
catalog.putAnimal(cat);
catalog.putAnimal(baldEagle);
catalog.showList();
}
}
Test Output
Cat: meow
BaldEagle: whistle (can stay in air for 50.0 km)
Updated 2019-10-09 Adding example for the "not-okay" case:
We could have dropped the CanFly
interface, and in the showList()
method, we apply the instanceof
on the concrete implementation BaldEagle
-- like this:
public void showList() {
animals.forEach(animal -> {
StringBuilder sb = new StringBuilder();
sb.append(animal.getName() + ": ");
sb.append(animal.makeNoise() + " ");
if (animal instanceof BaldEagle) {
sb.append(String.format(" (can stay in air for %s km)",
((BaldEagle) animal).getMaxInAirDistanceKm()));
}
System.out.println(sb.toString());
});
}
This approach is not okay because the code is now dependent on implementation, not interface. It prevents, for example, swapping out another implementation representing Bald Eagle (e.g. BaldEagleImpl
)