Java - An invalid case for dynamic dispatch

[Updated: May 17, 2017, Created: May 16, 2017]

In Java language, dynamic dispatch is the process where the decision of which polymorphic method will run is made during the runtime. The rule is very simple: the method to run belongs to the actual runtime type, not the declared type. For example:

A a = new B();
a.doSomething(..)

If both classes A and B have doSomething() method, then B#doSomething() should run because that's the actual runtime object type.

But there's a scenario where a developer might make mistake. For example following code tries to remove the element at 0 index from a List twice (second time on it's super type Collection):

public class CollectionScenario {
  public static void main(String[] args) {
      List<Integer> list = new ArrayList<>();
      list.add(10);
      list.add(20);
      System.out.println(list);

      list.remove(0);
      System.out.println(list);

      Collection<Integer> c = list;
      c.remove(0);
      System.out.println(list);
  }
}

Output

[10, 20]
[20]
[20]

Why c.remove(0) does not work in above example? To understand that, let's see how remove() methods are defined in the two interfaces:

public interface Collection<E> extends Iterable<E> {
    .....
    boolean remove(Object o);
    .....
}
public interface List<E> extends Collection<E> {
    .....
    boolean remove(Object o);
    E remove(int index);
    .....
}

Collection#remove(0) actually calls the Collection#remove(Object o) method and not the List#remove(int index) method. The reason is very simple: Collection class does not define the method E remove(int index), so it's actually not overridden by the List class. That means remove(index) is a not polymorphic method, so dynamic dispatch does not apply here.

To understand this scenario clearly, let's see another example

public class GeneralScenario {
    public static class A {
        public void doSomething(Object obj) {
            System.out.println("A#doSomething(Object) " + obj);
        }
    }

    public static class B extends A {
        public void doSomething(int i) {
            System.out.println("B#doSomething(int) " + i);
        }
    }

    public static void main(String[] args) {
        B b = new B();
        b.doSomething(1);

        A a = b;
        a.doSomething(1);
    }
}

Output

B#doSomething(int) 1
A#doSomething(Object) 1

Just like collection example, here also a.doSomething(1) call does not seem to be dispatched to the actual runtime type. Remember, dynamic dispatch is a two step process:

  1. Compile time method binding: When compiler see statement like this:
    A a = new B();
    a.doSomething(0)
    
    it binds the a.doSomething(0) method call to A#doSometing(Object). Here compiler never sees what's the assignment on the right side (for compiler, right side just has to be assignable to the left side, it does not do in depth analysis of what methods being overridden at all).
  2. Runtime method invocation: The JVM runtime will only dynamically dispatch a method call to it's subtype, if it is actually overridden. In this example no method overridden so no dynamic dispatch and the compiler decision of invoking A#doSometing(Object) is used.

Let's define doSomething(int) in class A, so that it will be overridden by B:

public class GeneralScenario2 {
    public static class A {
        public void doSomething(Object obj) {
            System.out.println("A#doSomething(Object) " + obj);
        }

        public void doSomething(int i) {
            System.out.println("A#doSomething(int) " + i);
        }
    }

    public static class B extends A {
        public void doSomething(int i) {
            System.out.println("B#doSomething(int) " + i);
        }
    }

    public static void main(String[] args) {
        B b = new B();
        b.doSomething(1);

        A a = b;
        a.doSomething(1);
    }
}

Output

B#doSomething(int) 1
B#doSomething(int) 1

Now compiler first binds the a.doSomething(1) call to A#doSomthing(int i) because it has the most specific matching method parameter. During runtime, JVM dynamically dispatches the call to the overridden version:
B#doSomething(int i).

Example Project

Dependencies and Technologies Used :

  • JDK 1.8
  • Maven 3.3.9

Invalid Method Disptach Example Select All Download
  • dynamic-dispatch-problem
    • src
      • main
        • java
          • com
            • logicbig
              • example

See Also