In What does this code print? Eric Gunnerson shows this C# code (int derives from object in C#):

using System;

public class A {
    public void F(int i) {
        Console.WriteLine("A: {0}", i);
    }
}

public class B : A {
    public void F(object o) {
        Console.WriteLine("B: {0}", o);
    }
}

public class Test {
    public static void Main() {
        B b = new B();
        b.F(1);
    }
}

And the answer B: 1 surprised me. So I tried the equivalent in Java (three source files)

public class A {
    public void f(Integer i) {
        System.err.println("A: " + i.toString());
    }
}

public class B extends A {
    public void f(Object o) {
        System.err.println("B: " + o.toString());
    }
}

public class Test {
    public static void main(String[] args) {
        B b = new B();
        b.f(new Integer(1));
    }
}

and it printed A: 1. What I expected.

Before I dig deeper into the Java case, let's see why the C# version works the way it does. Eric explains that method dispatch at runtime will occur on the most derived class that holds a method that could satisfy a certain method invocation and then will search for the best match in that class. So here the runtime will find that F in B does match the invocation in Test and is never going to look at A.

The reason for this behavior, he says, is versioning. If you invoke any method on B and some time later anybody introduces a new method into A that would better match your invocation the runtime will still choose B's method.

So how does it work in Java? Sometimes I enjoy digging into the JLS, sick, I know. Java performs much of the method resolution at compile time. At compile time it will determine which method signature matches a certain invocation best - and will select A - and at runtime it will use the method in the most derived class that has the same signature which was determined at compile time.

This has interesting consequences. If you remove the definition of f from A and recompile everything, the the code will (certainly) print B: 1. Now add back f in A and only recompile A but not Test, it still prints B: 1. Only if you recompile Test as well, it will return to A: 1.

In a way Java solves the versioning problem, but only as long as you don't recompile your code against a newer version of A. While C#'s way is counterintuitive (to a seasoned Java developer, at least), it is more predictable at the same time. I'm not sure which way I prefer.

path: /en/dotNet | #