Confusing polymorphism in Java

Consider this code (full class, works fine, all classes in one class for brevity).

My questions after the code list:

import java.util.LinkedList; import java.util.List; class Gadget { public void switchon() { System.out.println("Gadget is Switching on!"); } } interface switchonable { void switchon(); } class Smartphone extends Gadget implements switchonable { @Override public void switchon() { System.out.println("Smartphone is switching on!"); } } class DemoPersonnel { public void demo(Gadget g) { System.out.println("Demoing a gadget"); } public void demo(Smartphone s) { System.out.println("Demoing a smartphone"); } } public class DT { /** * @param args */ public static void main(String[] args) { List<Gadget> l = new LinkedList<Gadget>(); l.add(new Gadget()); l.add(new Smartphone()); for (Gadget gadget : l) { gadget.switchon(); } DemoPersonnel p = new DemoPersonnel(); for (Gadget gadget : l) { p.demo(gadget); } } } 

Questions:

  • From a compiler point of view, what is the origin of the switchon method in a smartphone? Does it inherit from the base class Gadget? Or is it an implementation of the switching method provided by the switchable interface? Does annotation mean here?

  • In the main method, in the first loop: Here we see a case of polymorphism at runtime - i.e. when the first loop of the cycle is started and gadget.switchon () is called, it first prints “Gadget switches to” and then prints “Smartphone is on”. But in the second cycle, this permission does not occur at run time, and the output for both calls to the demo mode is “demonstration of the gadget”, while I expected it to print “Demoing the gadget” the first iteration and “Demoing smartphone”, the second time.

What do I misunderstand? Why does the runtime allow the child class in the first for loop, but does not do this in the second loop?

Finally, a link to a clear tutorial on run-time / compilation politicism in Java will be appreciated. (Please do not post links to tutorials on the Java curriculum, I did not find the material particularly impressive when discussing more subtle nuances at a decent depth).

+8
java polymorphism
source share
8 answers

Here's how it works in the near future:
Compilation time

  • The compiler determines the required signature for the requested method.
  • Once the signature is defined, the compiler starts looking for it in the class type
  • If it finds a compatible candidate method with the required signature receipts, otherwise an error is returned

lead time

  • At run time, the JVM starts looking for a candidate method with a signature as exactly defined at compile time.
  • The search for the executable method actually begins with the real class of the object implementation (which may be a subclass of the type-class) and look at the entire hierarchy.

Your list is defined using a gadget type.

 for (Gadget gadget : l) { gadget.switchon(); } 

When you request gadget.switchon(); , the compiler will look for the switchon() method in the Gadget class, and, as there, the candidate’s signature is simply confirmed as switchon() .

At run time, the JVM will look for the switchon() method from the smartphone class , which is why it displays the correct message.

This is what happens in the second for-loop

 DemoPersonnel p = new DemoPersonnel(); for (Gadget gadget : l) { p.demo(gadget); } 

The signature in this case is intended for both demo(Gadget g) objects, therefore the demo(Gadget g) method is executed for both iterations.

Hope this helps!

+4
source share

From a compiler point of view, what is the origin of the switchon method in a smartphone? Does it inherit from the base class Gadget? Or is it an implementation of the switching method provided by the switchable interface?

Second case

Is there any difference here?

Not at all, @Override is just a helper because you use it, you tell the compiler: "I intend to override the method from the supertype, please throw an exception and don't put it on," t compile it if it doesn't cancel anything "

About the second question, in this case, the method that better matches its signature is the one that needs to be called. At run time, in the second loop, your objects have an associated super-type, which will cause the reason for the public demonstration of void (Gadget g) , and not the public demonstration of void (smartphone g)

+1
source share

1. It does not matter. Since it extends the gadget, if you do not redefine and call switchon () from the smartphone, it says: “The gadget is on!”. When you have both an interface and a parent class with the same method, it really doesn't matter.

2. The first cycle works, and the second - not because java looks at objects. When you call a method from an object, it takes the method directly from that object and thus knows if there is a smartphone or gadget. When you send a smartphone or gadget to an overloaded method, everything in this class is called a gadget, whether it is actually a smartphone or not. Because of this, he uses the gadget method. To complete this work, you would like to use it in the DemoPersonnel demo (Gadget g) method:

 if(gadget instanceof Smartphone){ System.out.println("Demoing a gadget"); }else{ System.out.println("Demoing a smartphone"); } 

Sorry, I don’t have a link to the tutorial that I learned due to a combination of AP Computer Science and experience.

0
source share
  • The annotations are irrelevant here. Technically, it looks like you are doing both: redefine the parent switchon () and implement the switchon () interface method in one shot.

  • A method search (with respect to the method arguments) is not performed dynamically (at run time), but statically at compile time. It looks weird, but here's how it works.

Hope this helps.

0
source share

First answer question 2: in the second loop, you pass in the object typed as a gadget, so the best match in the demo class is the method that takes the gadget. It is allowed compile time.

for question 1: the annotation does not matter. it just indicates that you are redefining (implementing) the method in the interface.

0
source share
  • For the compiler, the smartphone inherits the "implementation" switchon () method from Gadget, and then Smartphone overrides the inherited implementation with its own implementation. On the other hand, the switching interface is dictated by the Smartphone to provide an implementation of the definition of the switchon () method and which was implemented by the implementation redefined in the Smartphone.

  • The first case works as you expected, because it is really a case of polymorphism, i.e. You have one contract and two implementations: one in gadgets and the other in a smartphone; where later "redefined" the previous implementation. The second case "should not" work as you expect, because there is only one contract and one implementation. Note that you are not overriding the demo () method, you are actually overloading the demo () method. And overloading means two "different" unique method definitions that share only the "same name". Thus, this is the case of one contract and one implementation, when you call demo () with the Gadget parameter, because the compiler will match the method name with the exact types of the method parameters and thus will call “different methods” in both iteration of the loop.

0
source share

About the second question: In Java, dynamic method dispatch occurs only for the object on which the method is called, and not for parameter types of overloaded methods.

Here is a link to the java language specification .

As they say:

When calling a method (§15.12), the number of actual arguments (and any explicit type arguments) and compilation time types arguments are used at compile time to determine the signature of the method that will be called (§15.12.2). If the method that should be invoked is an instance method, the actual method to be called will be determined at run time using the dynamic method search (§15.12.4).

So basically: the type of compilation time of the method parameters is used to determine the signature of the method, which will be called

At run time, the class of the object on which the method is called determines which implementation of this method is called, given that it can be an instance of a subclass of the declared type that overrides the method.

In your case, when you create an object of a child class with new child (); and pass it to the overloaded method, it has a superclass type. Therefore, an overloaded method is called with the parent.

0
source share

The compiler selects the method signature based on the declared pointer type "this" for the method and the declared parameter type. Since switchon gets the "this" Gadget pointer, that is, the version of the method that the compiler will reference in its generated code. Of course, run-time polymorphism can change that.

But polymorphism at run time applies only to the pointer to the "this" method, and not to parms, so the choice of a method signature compiler will be "controlled" in the second case.

0
source share

All Articles