Reputation: 316
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?
Upvotes: 5
Views: 391
Reputation: 3412
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.
Upvotes: 4
Reputation: 24344
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.Upvotes: 1
Reputation: 508
Your reference type is A
and A
has only one method show(A obj)
which has been overridden in B
and printing B and A
, that is why you are getting B and A
printed always.
Upvotes: 0
Reputation: 98
I think your question relate to another topic - Distinguishing between an Object and a Reference. From Certified Professional SE 8 Programmer II: In Java, all objects are accessed by reference, so as a developer you never have direct access to the memory of the object itself. Conceptually, though, you should consider the object as the entity that exists in memory, allocated by the Java runtime environment. Regardless of the type of the reference that you have for the object in memory, the object itself doesn’t change. For example, since all objects inherit java.lang.Object, they can all be reassigned to java.lang.Object, as shown in the following example:
Lemur lemur = new Lemur();
Object lemurAsObject = lemur;
Even though the Lemur object has been assigned a reference with a different type, the object itself has not changed and still exists as a Lemur object in memory. What has changed, then, is our ability to access methods within the Lemur class with the lemurAsObject reference. Without an explicit cast back to Lemur, as you’ll see in the next section, we no longer have access to the Lemur properties of the object.
We can summarize this principle with the following two rules:
Upvotes: 0