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:
a
) and get its declared type. This type is A
.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
.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.
a
.a = new B()
, this type is B
.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.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.
In your example A a = new B()
, a
is a polymorphic reference - a reference that can point different object from the class hierarchy (in this case it is a reference to object of type B
but could also be used as a reference to object of class A
, which is topmost in the object hierarchy).
As for specific behaviour you are asking about:
B
printed in the output?Which specific show(B obj)
method will be invoked through the reference variable depends on the reference to the object it holds at a certain point in time. That is: if it holds reference to object of class B
, a method from that class will be called (that is your case) but if it would point to an object of class A
, a reference of that object would be called. That explains why B
is printed in the output.
hierarchy).
and A
printed in the output?Method in a subclass with the same name but different signature is called method overloading. It uses static binding, which means that the appropriate method will be bound at compile-time. The compiler has no clue about the runtime type of your objects.
So show(A obj)
of class A
will be bound in that case. However, when the method will be actually called in runtime, its implementation from class B
will be invoked (show(A obj)
from class B
) and that is why you see B and A
and not A and A
in the output.
Reference for invokevirutal
(an JVM instruction called when virtual methods are executed):
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 the a.show(c)
, the same rules apply as for B
because C
doesn't have any methods overloaded and it is directly inheriting from B
.
EDIT:
Step by step explanation of why a.show(c)
prints B and A
:
a
to be objectref
to object of class A
(compile-time)a
is of type A
, method A::show(A obj)
is bound.show()
method is invoked on object a
), runtime recognizes that reference a
polymorphically points to object of type B
(that's because of A a = new B()
) (runtime)C extends B
, runtime treats a.show(c)
as it would treat b.show(c)
(or b.show(b)
), so B::show(A obj)
is used in this case but in place of obj
an object of type B
is used. That's why "B and A" is being printed.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With