I have the code for a general case:
public class A {
public String show(A obj) {
return ("A and A");
}
}
public class B extends A {
public String show(B obj) {
return ("B and B");
}
public String show(A obj) {
return ("B and A");
}
}
public class C extends B {
}
public class Test {
public static void main(String[] args) {
A a = new B();
B b = new B();
C c = new C();
System.out.println("1--" + a.show(b));
System.out.println("2--" + a.show(c));
}
}
The results are:
1--B and A
2--B and A
I know there is a priority chain from high to low in Java:
this.show(O), super.show(O), this.show((super)O), super.show((super)O)
My understanding is below:
In this code:
A a = new B()
An upcast happens. A is a parent class reference and B is a child parent class reference. When the code is compiled and run, the child parent class reference determines how the method is chosen. In this case, show(A)
in class B is chosen.
There is also a requirement that polymorphism has to meet: The method that is chosen should be included in the parent class definition.
Could someone give a more detailed explanation on the result shown?
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 isA
. - In class
A
and all of its super types, make a list of all methods that are namedshow
. The compiler will find onlyshow(A)
. It does not look at any methods inB
orC
. - From the list of found methods, choose the one that best matches the parameter (
b
) if any.show(A)
will acceptb
, 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 isB
. - Look in
B
and try to find the methodshow(A)
. This method is found sinceB
overrides it. If this had not been the case, it would look in the super classes (A
andObject
) until such a method is found. It is important to note that it only considersshow(A)
methods, so eg.show(B)
fromB
is never considered. - The runtime will now call method
show(A)
fromB
, giving theString
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.