5

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?

Boann
  • 44,932
  • 13
  • 106
  • 138
CandyCrusher
  • 238
  • 1
  • 12

4 Answers4

3

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:

  1. Look at the object that the method is called on (a) and get its declared type. This type is A.
  2. 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.
  3. 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.

  1. Get the object on which the method is called (which is already on the stack by then), which is a.
  2. Look at the actual runtime type of this object. Since a = new B(), this type is B.
  3. 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.
  4. 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.

Community
  • 1
  • 1
TiiJ7
  • 3,152
  • 4
  • 25
  • 35
  • What is the "objectref" and "resolved method" in my case? – CandyCrusher Oct 12 '18 at 10:36
  • How can this reference explain my case, could u point out step by step. – CandyCrusher Oct 12 '18 at 10:50
  • 1) The compiler checks `A` (the declared type of `a`) to find a suitable `show` method. 2) Compiler resolves `show(A)` and adds the `invokevirtual` instruction for it. 3) Runtime needs to execute the instruction. 4) Runtime looks at `a` (=`objectref`) and gets its class `B`. 5) Runtime checks `B` for an override of the resolved method `show(A)`. 6) Runtime finds the `show(A)` in `B` and the lookup procedure terminates (**note**: `show(B)` is a separate method, and does *not* override `show(A)`, so it is *not* considered) 7) Runtime calls `show(A)` from class `B`. – TiiJ7 Oct 12 '18 at 11:08
  • That is correct. But how about "a.show(c)", in that case, there is not show(C) in B, even it can inherit its parent why it didn't invoke show(B)? – CandyCrusher Oct 12 '18 at 11:20
  • @CandyCrusher I've added a more detailed explanation. I hope this will explain everything properly to you now. The most important thing is that you know where the compiler and runtime look when they are considering methods. – TiiJ7 Oct 12 '18 at 12:49
  • @TiiJ7, when you say `...super types..` what do you mean ?? – Vishwa Ratna Sep 14 '20 at 05:02
  • 1
    @VishwaRatna I assume you mean my compilation point 2. By "super types" I mean all types ([classes/interfaces](https://stackoverflow.com/questions/16600750/difference-between-class-and-type)) that are [inherited](https://docs.oracle.com/javase/tutorial/java/IandI/subclasses.html) from. For `A` that is just `Object` (which it extends implicitly); for `B` the super types are both `A` and `Object`. Of course, `Object` does not have a `show` method, but if it had, the compiler would have to consider it since it could be more specific. Sorry if my explanations are a bit confusing. – TiiJ7 Sep 14 '20 at 07:33
1

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:

Why is 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).

Why is 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:

  1. Compiler recognizes object a to be objectref to object of class A (compile-time)
  2. Because a is of type A, method A::show(A obj) is bound.
  3. When the code is actually executed (i.e. 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)
  4. Because 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.
syntagma
  • 20,485
  • 13
  • 66
  • 121
  • Oh, cool. Thanks for confirmation. I understand overloading and overriding separately, but I am confused when they come together. From my understand on overloading, in the first line of my result, show(B obj) in Class B is expected to invoked which is not correct in fact. – CandyCrusher Oct 12 '18 at 10:42
  • At compile-time `String show(A obj)` from `A` will be bound and that's method overloading. However, at runtime, method with the same signature `String show(A obj)` but from `B` will be called and that's what I try to explain in my answer. – syntagma Oct 12 '18 at 10:55
  • Could you help me to explain the second line of result I get step by step? thanks! – CandyCrusher Oct 12 '18 at 11:01
  • @CandyCrusher I've added this to my answer. – syntagma Oct 12 '18 at 12:10
  • @syntagma nice explanation. – Vishwa Ratna Sep 14 '20 at 05:12
0

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:

  1. The type of the object determines which properties exist within the object in memory.
  2. The type of the reference to the object determines which methods and variables are accessible to the Java program.
0

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.

Alok Singh
  • 394
  • 1
  • 5