How does polymorphism in Java work for this general case (method with parameter)?

CandyCrusher :

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?

TiiJ7 :

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.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=82738&siteId=1