In order to get why you get the result B and A
twice, you need to know that there are 2 parts to this: compilation and runtime.
Compilation
When encountering the statement a.show(b)
, the compiler takes these basic steps:
- Look at the object that the method is called on (
a
) and get its declared type. This type is A
.
- In class
A
and all of its super types, make a list of all methods that are named show
. The compiler will find only show(A)
. It does not look at any methods in B
or C
.
- From the list of found methods, choose the one that best matches the parameter (
b
) if any. show(A)
will accept b
, so this method is chosen.
The same thing will happen for the second call where you pass c
. The first two steps are the same, and the third step will again find show(A)
since there is only one, and it also matches the parameter c
. So, for both of your calls, the rest of the process is the same.
Once the compiler has figured out what method it needs, it will create a byte-code instruction invokevirtual
, and put the resolved method show(A)
as the one it should call (as shown in Eclipse by opening the .class
):
invokevirtual org.example.A.show(org.example.A) : java.lang.String [35]
Runtime
The runtime, when it eventually gets to the invokevirtual
needs to do a few steps also.
- Get the object on which the method is called (which is already on the stack by then), which is
a
.
- Look at the actual runtime type of this object. Since
a = new B()
, this type is B
.
- Look in
B
and try to find the method show(A)
. This method is found since B
overrides it. If this had not been the case, it would look in the super classes (A
and Object
) until such a method is found. It is important to note that it only considers show(A)
methods, so eg. show(B)
from B
is never considered.
- The runtime will now call method
show(A)
from B
, giving the String
B and A
as result.
More detail about this is given in the spec for invokevirtual
:
If the resolved method is not signature polymorphic (§2.9), then the invokevirtual instruction proceeds as follows.
Let C be the class of objectref. The actual method to be invoked is selected by the following lookup procedure:
If C contains a declaration for an instance method m that overrides (§5.4.5) the resolved method, then m is the method to be invoked, and the lookup procedure terminates.
Otherwise, if C has a superclass, this same lookup procedure is performed recursively using the direct superclass of C; the method to be invoked is the result of the recursive invocation of this lookup procedure.
Otherwise, an AbstractMethodError is raised.
For your example, the objectref
is a
, its class is B
and the resolved method is the one from the invokevirtual
(show(A)
from A
)
tl:dr - Compile-time determines what method to call, runtime determines where to call it from.