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 | #