2

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; using instanceof 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)

leeyuiwah
  • 6,562
  • 8
  • 41
  • 71
  • 1
    Is there any reason why you are posting this answer as a new question instead of replying to the original question you linked to? – Elias Oct 09 '19 at 09:18
  • @Elias -- I feel that other question is on how to use it (i.e. make sure that the usage is syntatically correct) while my question is more on the design question (for all the syntatically correct use cases, what are justified usage, what are not) – leeyuiwah Oct 09 '19 at 09:24
  • very hard (and probably wrong) to have a general rule to state if it "is okay" – user85421 Oct 09 '19 at 09:43
  • you better give an example of the "not okay" case to have a discussion. your example looks ok for me – Vault23 Oct 09 '19 at 09:50
  • 3
    An example when `instanceof` is absolutely necessary always is meta programming. When you have to deal with dynamic type systems that maybe are even loaded at runtime and you have to do some type investigations. For example stuff that a debugger does. Or things like GSON or logger which just have to deal at lot with dynamic types due to their nature. That is also the type of code that naturally needs to use the reflection API, which is otherwise generally considered bad. – Zabuzard Oct 09 '19 at 09:55
  • I think that the question, at its core, is justified, even though the part of "interface vs. implementation" distracts a little from this core. I even considered asking this question, in a somewhat more generic form - namely, whether "type tests" (regardless of whether it's `instanceof` or something else) can theoretically be avoided. – Marco13 Oct 09 '19 at 12:31
  • @Marco13, I would certainly be interested in a more theoretical version of this question, which could be less subjective. – jaco0646 Oct 09 '19 at 13:42
  • 1
    @Vault23 -- I updated my question to also include example for the "not okay" case. Thanks! – leeyuiwah Oct 09 '19 at 15:25

3 Answers3

0

I think people assume that's there is always a "cleaner" solution to produce the kind of behavior you want.

In your example, I would say that the use of Visitor design pattern do exactly the same without the using of instanceOf :

public interface Animal {
    String getName();
    String makeNoise();
    void accept(AnimalVisitor v);
}

public interface AnimalVisitor() {
    void visit(Cat a);
    void visit(BaldEagle a);
}

public interface CanFly {
    float getMaxInAirDistanceKm();
}

public class Cat implements Animal {
    void accept(Visitor v) {
        v.visit(this);
    }
}

public class BaldEagle implements Animal, CanFly {
    void accept(Visitor v) {
        v.visit(this);
    }
}

public class DisplayVisitor implements AnimalVisitor  {
    void visit(Cat a) {
       //build & display your string
    }

    void visit(BaldEagle a) {
       //build & display your string
    }
}

public class Catalog {
    private List<Animal> animals = new ArrayList<>();

    public void putAnimal(Animal animal) {
        animals.add(animal);
    }

    public void showList() {
        DisplayVisitor display = new DisplayVisitor();
        animals.forEach(a->a.accept(display));
    }
}

Although I do not answer completely to your question, it shows that the same behavior can be accomplished without the use of instanceOf in most of cases, just by thinking in OOP way and using known patterns.

LostReality
  • 657
  • 2
  • 8
  • 33
  • Thanks for the suggestion. However, I noticed that now `AnimalVisitor` is coupled with the concrete classes `Cat` and `BaldEagle`). I have tried a variant of your design by changing the second `visit()` method of `AnimalVisitor` to take an argument of `CanFly` (interface) instead of `BaldEagle` (a concrete class), but then that also not work. Because some of the processing of involves some method that is of `Animal` not `CanFly` (in this case `getName()` and `makeNoise()`) – leeyuiwah Oct 10 '19 at 16:29
  • Why pass CanFly interface as argument ? You need to implement 1 visitor class for each concrete class if you want this pattern to work. Let's say you want to create a new "GoldFish" class, you would need to add "void visit(GoldFish a)" to visitor interface and then implement the logic in DisplayVisitor class. – LostReality Oct 11 '19 at 07:45
  • Thanks! That is exactly my point. In the Visitor Pattern, `AnimalVisitor` is tightly coupled with concrete classes (`Cat`, `BaldEagle`) etc. In contrast, using `instanceof` with `CanFly`, the code is dependent on only an interface not concrete classes. – leeyuiwah Oct 11 '19 at 07:54
  • I mean its seems depending on an interface is more flexible than depending on concrete subclassses. What do you think? – leeyuiwah Oct 11 '19 at 07:54
  • You are right but you need a bit of work-around if you want even more flexibility. In your case, I would : change Animal interface to a (abstract?) class and same for CanFly that I would change to a FlyingAnimal class (which would inherits from Animal) then FlyingAnimal can be passed to Visitor with all methods needed available. – LostReality Oct 11 '19 at 09:38
  • I tried that and it did not work, for the following reason. Methods `getName()` and `makeNoise()` can either be defined for `Animal` or `Flying Animal`. In the former case, `DisplayVisitor#visit(FlyingAnimal fa)` will not be able to use them. In the latter case, `DisplayVisitor#(Animal a)` will not be able to use them. There will be more code duplication and less reuse. – leeyuiwah Oct 11 '19 at 09:49
  • Sorry I would take back my last comment (about the visitor can't be made depending on interface). I just realized that if `FlyingAnimal extends Animal`, and there is a method `AnimalVisitor#visit(FlyingAnimal fa)`, then this method can get to use all three methods -- `getName()` and `makeNoise()` (from being an `Animal`) and `getMaxInAirDistanceKm()` from being a `FlyingAnimal` – leeyuiwah Oct 11 '19 at 13:19
0

However, it does not further elaborate when the use of instanceof is okay

This is not a direct answer to your question, but I'd say that instanceof is only appropriate when all other options aren't feasible.

Using instanceof on an interface is okay; using instanceof on an implementation is not okay

I would re-phrase that as "using instanceof on an interface is less bad than using instanceof on an implementation", but that's just a corollary of the general rule that strong coupling is bad. There are usually better alternatives.

When you want to use instanceof, you should think about introducing additional interfaces or interface methods or using the visitor pattern (see other answer) first. All of these options are cleaner ways to achieve the desired behavior in Java.

This isn't always elegant and can require artificial interfaces or cause interface bloat, which is why some other languages support ad-hoc union types and algebraic data types. But instanceof is not a good way of emulating either, since Java's type system won't help you make sure that you're handling all possible options.

CodeMonkey
  • 704
  • 1
  • 6
  • 15
  • My objective is to solicit concrete guidelines of when the use of `instanceof` is justified -- simply saying "use it as a last resort" is too vague. I suspect that using `instanceof` on an interface is justified -- the Visitor Pattern can avoid `instanceof` but introduces strong coupling between the `Visitor` (both its interface and concrete implementations) on the concrete subclasses (`Cat`, `BaldEagle`). So it has its own draw back (see my comment on @LostReality's answer above). Another justified use is metaprogramming, as @Zabuza has pointed out (in the comment of the Q) – leeyuiwah Oct 11 '19 at 08:36
  • 1
    @leeyuiwah "the Visitor Pattern can avoid instanceof but introduces strong coupling between the Visitor (both its interface and concrete implementations) on the concrete subclasses (Cat, BaldEagle)." That is incorrect, but I see that you already figured it out in the comments below. The visitor pattern is more or less functionally equivalent to `instanceof` + downcasting (since the whole point of the pattern is [double dispatch](https://en.wikipedia.org/wiki/Double_dispatch)), so you don't always have to mention concrete implementations. – CodeMonkey Oct 11 '19 at 15:51
  • Also, I'm sorry for not being able to provide a better answer with concrete examples that weren't mentioned yet. I guess I was mostly trying to say that you shouldn't fight established best practices unless you have a really compelling reason. – CodeMonkey Oct 11 '19 at 15:56
0

To start out, it's important to note that the Object-Oriented Programming paradigm is the source of resistance to type checking such as instanceof. Other paradigms do not necessarily share this resistance, and may even encourage type checking. So this question is really only relevant if you're trying to do OOP.

If you are trying to do OOP, then you should be leveraging polymorphism as much as you can. Polymorphism is OOP's primary weapon. Type checking is the antithesis of polymorphism.

Certainly type checking of abstractions is preferable to type checking of concrete implementations; but that's just restating the Dependency Inversion Principle (depend on abstractions, not concretions).

In OOP, every usage of type checking can be viewed as a missed opportunity for polymorphism.

jaco0646
  • 15,303
  • 7
  • 59
  • 83