Skip to content

Instanceof vs. polimorphism

An instanceof check is often an indication for misplaced source code. Some developer say that instanceof checks are ugly code, but there are reasons against instancof checks beyond a subjective feeling. In this blog I want to compare instanceof checks with polymorphism and discuss the downsides. This blog is intended to be used as a basis for desing decisions and discussions about code design.

Instanceof motivations

Everytime we write an instanceof check in java we are interessted in the capabilities of an object. We want to know if an object has specific features or methods or if it has not. This can also be implicit features that are derived from the type.

In this case we have 2 options:

  • Introduce instanceof checks to reconstitue the missing type information.
  • Use polimorphism and extend the actual type by adding an abstract method and subtypes can implement it differently.

Both options solve the same problem: “How to execute different code depending on a type”.

Executing different code depending on some criteria means introducing a control flow. The difference between the two options above is who introduces the controlflow:

  • With instanceof check we introduce the control flow with a control-flow statement (if-statement).
  • With polimorphism the compiler introduces the control flow using a bytecode instruction like invokevirtual.

At first sight both options might look equal, but the slight difference has a big impact on code quality as we will see soon. But first we should take a closer look at instanceof checks and polymorphism.

Instanceof vs. Polimorphism

So what are the differences between instanceof checks and polimorphism? In order to answer this question we must take a look at how and where the control flow is implemented in both cases. Therefore I want to introduce some example code here.

Instanceof example

In the following example I use a variant of a code snippet that I found in the book “Clean Code”, by Robert C. Martin to show an instanceof check scenario.

public class PaymentCalculator {
    public static Money calculatePay(Employee e) throws InvalidEmployeeType {
        if(e instanceof CommissionedEmployee){
            return calculateCommissionedPay(e);
        } else if(e instanceof HourlyEmployee){
            return calculateHourlyPay(e);
        } else if(e instanceof SalariedEmploye){
            return calculateSalariedPay(e);
        } else {
            throw new InvalidEmployeeType(e);
        }
    }
}

Now lets take a look at the client code that uses a PaymentCalculator.

Employee employee = ...;
Money money = PaymentCalculator.calculatePay(employee);

As we can see the control flow statements are all placed in the PaymentCalculator and the client code is control flow free.

Polimorphism example

We can transform the instanceof example above to a polymorphic solution by moving the type sepcific logic into a conrete type and introducing an abstract method in the super type.

public abstract class Employee {
     public abstract Money calculatePay();
}

public class CommissionedEmployee extends Employee {
    public Money calculatePay(){
        // Commissioned employee payment calulation here...
    }
}

public class HourlyEmployee extends Employee {
    public Money calculatePay(){
        // Hourly employee payment calulation here...
    }
}

public class SalariedEmploye extends Employee {
    public Money calculatePay(){
        // Saleried employee payment calulation here...
    }
}

The client side will then just call calculatePay() on an Employee object and does not need to pass an employee reference to it.

Employee employee = ...;
Money money = employee.calculatePay();

In this case the selection of the right method to be executed is done at runtime on the client side. The compiler will introduce an invokevirtual bytecode instruction in the client code. This bytecode instruction dispatches the virtual call to calculatePay() to the right method based on the runtime type of the employee object.

Comparision of instanceof vs. polimorphism

  • Extendability
    If a new type must be added the polymorphic solution is better, because you only need to implement the new type and you don’t have to know that there is another place (like the PaymentCalculator) that you must extend as well.
  • Single Responsibility Principle
    Since instanceof checks are used to execute type specific code, they add another responsibility to a method when you use them. In the example above the calculatePay() method is responsible to calculate the payment for every employee type. This means that this method changes everytime the payment calculation for one employee type is changed.  If you make changes in this method you might also need to understand the other type’s calculation logic and you might affect other calculation logic.
    In constrast to this the polimorphism soltution puts every code in the suitable class. If you want to change the calculation logic of only a SalariedEmployee, you only have to edit this class’s calculatePay() method.
  • Runtime performance
    The invokevirtual bytecode instruction that a compiler generates in the client code dispatches the virtual method call to the right method at runtime depending on the type. This is much faster as an instanceof check. Furthermore the dispatch of a virtual method call to the right method always takes the same time and does not depend on the runtime types. E.g.
    If a lot of SalariedEmployee objects are passed to the calculatePay method at runtime a lot of unnecessary instanceof checks are done until the right code to execute is selected. If CommisionedEmployee objects are passed to the calculatePay method at runtime then only one instanceof check is done. So you might want to rearrange the instanceof checks order to match the probability of occurence of the employee types to reduce unnecessary instanceof checks. But this has a big downside. The probability of occurence of an employee type depends on the runtime data and the runtime data might change often. In other words if the probability of occurence is dynamic then the order of instanceof checks doesn’t make any sense.
  • Refactoring
    What will happen if you want to refactor the type hierarchy of a class that is used in instanceof checks? An example will show the problem. Let’s assume you have the following class hierarchy.

    class A {}
    
    class B extends A{}
    
    class C extends A{}

    and a method the does instanceof checks like this one:

    public void someMethod(A a){
       if(a instanceof B){
          System.out.println("B");
       } else if(a instanceof C){
          System.out.println("C");
       } else {
          System.out.println("A");
       }
    }

    Now if you refactor the class hierarchy to this one

    class A {}
    
    class B extends A {}
    
    class C extends B {}

    you must also rearange the instanceof checks order. Otherwise an instance of type will print out “B”, because the instanceof B check comes before the instanceof C check. If you only have one place where those instanceof checks are done and you know this place, then you are lucky. But what will happen if there are a lot of places and you forget one? What if a new colleague who is not aware of all instanceof check places modifies the code or you modify the code 3 month later? Hopefully you have enough tests that cover it or your application will react in a strange way. Would be better if it is not a real accounting system.

Conclusion

At least I think it will be better to avoid instance of checks as much as possible, because of the reasons I described the section above. I also showed that instanceof checks can be replaced with polimorphism if you find an abstraction. There are rare situations when you have to do instanceof checks, e.g. marker interfaces or when you implement infrastructure code like an framework. But even then you can avoid it in most situations.

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *

 

GDPR Cookie Consent with Real Cookie Banner