Search code examples
javapolymorphismlate-binding

Why is this Java method polymorphing by declared type and not runtime type?


This code:

public class PMTest
{

    private static class Runner { }
    private static class Server extends Runner { }

    private static class Task
    {
        public void delegate(Runner runner)
        {
            System.out.println("Task: " + runner.getClass().getName() +
                               " / " + this.getClass().getName());
        }
    }

    private static class Action extends Task
    {
        public void delegate(Server server)
        {
            System.out.println("Action: " + server.getClass().getName() +
                               " / " + this.getClass().getName());
        }
    }


    private static void foo(Task task, Runner runner)
    {
            task.delegate(runner);
    }

    private static void bar(Action task, Runner runner)
    {
            task.delegate(runner);
    }

    private static void baz(Action task, Server runner)
    {
            task.delegate(runner);
    }


    public static void main (String[] args)
    {
        try {
            Server server = new Server();
            Action action = new Action();

            action.delegate(server);
            foo(action, server);
            bar(action, server);
            baz(action, server);

        }
        catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

produces this output:

$ java PMTest
Action: PMTest$Server / PMTest$Action
Task: PMTest$Server / PMTest$Action
Task: PMTest$Server / PMTest$Action
Action: PMTest$Server / PMTest$Action

I can see very clearly that Task's method is being chosen over Action's method. I don't understand why, though, since the objects always know what they are and I thought Java's late-binding method selection would be able to distinguish the difference in method signatures. The call to bar() is especially confusing, as task is declared as an Action at that point.

If it makes a difference, this is Java 6:

$ java -version
java version "1.6.0_14"
Java(TM) SE Runtime Environment (build 1.6.0_14-b08)
BEA JRockit(R) (build R27.6.5-32_o-121899-1.6.0_14-20091001-2113-linux-ia32, compiled mode)

I can change my code to make it work, but I'd like to understand why it doesn't work. Thanks for the help!


Solution

  • That's how dispatch works in java.

    Dispatch is based first on static parameter types, and once a static signature has been chosen, then the runtime type of the object containing the method is used to figure out which overriding is used.

    For example, in

    void foo(Object o) {
      if (o instanceof Number) { foo((Number) o); }
      else if (o instanceof String) { foo((String) o); }
    }
    
    void foo(String s) { ... }
    
    void foo(Number n) { ... }
    
    { foo((Object) "foo"); }  // Calls foo(Object) which calls foo(String).
    { foo("foo"); }  // Calls foo(String) without first calling foo(Object).