user504674
user504674

Reputation:

Confusing polymorphism in Java

Consider this code (complete class, runs fine, all classes in one class for the sake of brevity).

My questions are after the code listing:

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:

  1. From the compilers point of view, what is the origin of the switchon method in Smartphone? Is it inherited from the base class Gadget? Or is it an implementation of the switchon method mandated by the switchonable interface? Does the annotation make any difference here?

  2. In the main method, first loop: Here, we see a case of runtime polymorphism - i.e., when the first for loop is running, and gadget.switchon() is called, it first prints "Gadget is switching on", and then it prints "Smartphone is switching on". But in the second loop, this runtime resolution does not happen, and the output for both calls to demo is "Demoing a gadget", whereas I was expecting it to print "Demoing a gadget" the first iteration, and "Demoing a smartphone" the second time.

What am I understanding wrong? Why does the runtime resolve the child class in the first for loop, but doesn't do so in the second for loop?

Lastly, a link to a lucid tutorial on runtime/compile-time polymorphism in Java will be appreciated. (Please do not post the Java tutorial trail links, I didn't find the material particularly impressive when discussing the finer nuances in respectable depth).

Upvotes: 8

Views: 908

Answers (8)

Hot Licks
Hot Licks

Reputation: 47699

The compiler selects a method signature based on the declared type of the "this" pointer for the method and the declared type of the parameters. So since switchon receives a "this" pointer of Gadget, that is the version of the method that the compiler will reference in its generated code. Of course, runtime polymorphism can change that.

But runtime polymorphism only applies to the method's "this" pointer, not the parms, so the compiler's choice of method signature will "rule" in the second case.

Upvotes: 0

jnardiello
jnardiello

Reputation: 699

This is how it works shortly:
Compiling time

  • The compiler defines the required signature for the requested method
  • Once the signature is defined, the compiler starts to look for it in the type-Class
  • If it finds any compatible candidate method with the required signature proceeds, otherwise returns an error

Runtime

  • During execution JVM starts to look for the candidate method with the signature as exactly defined during the compiling-time.
  • The search for the executable method actually starts from the real Object implementation Class (which can be a subclass of the type-Class) and surf the whole hierarchy up.

Your List is defined with type Gadget.

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

When you ask for gadget.switchon(); the compiler will look for the switchon() method in the Gadget class and as it's there the candidate signature is simply confirmed to be switchon().

During the execution, the JVM will look for a switchon() method from the Smartphone Class and this is why it is displaying the correct message.

Here 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 for both objects demo(Gadget g), this is why for both iterations method demo(Gadget g) is executed.

Hope it helps!

Upvotes: 4

jsfviky
jsfviky

Reputation: 183

  1. The annotation does not make any difference here. Techinically is like you are doing both things: overriding parent switchon() and implementing the switchon() interface method in one shot.

  2. Method lookup (with respect to method arguments) is not done dynamically (at runtime) but statically at compile-time. Looks strange but thats how it works.

Hope this helps.

Upvotes: 0

voidMainReturn
voidMainReturn

Reputation: 3513

about the second question : In Java, dynamic method dispatch happens only for the object the method is called on, not for the parameter types of overloaded methods.

Here is a link to the java language specification.

As it says :

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

so basically :the compile time type of the method parameters is used to determine the signature of the method to be called

At runtime, the class of the object the method is called on determines which implementation of that method is called, taking into account that it may be an instance of a subclass of the declared type that overrides the method.

In your case when you create object of a child class by new child(); and pass it on to the overloaded method, it has superclass type associated. Hence overloaded method with parent's object is called.

Upvotes: 0

Syed Muhammad Humayun
Syed Muhammad Humayun

Reputation: 459

  1. For compiler, Smartphone inherits switchon() method "implementation" from Gadget and then Smartphone overrides inherited implementation with its own implementation. On the other hand switchonable interface dictates Smartphone to provide an implementation of switchon() method definition and which was fulfilled by the implementation overridden in Smartphone.

  2. First case is working as you expected because it is indeed a case of polymorphism i.e. you have one contract and two implementations - one in Gadget and another in Smartphone; where later has "overridden" the former implementation. Second case "should not" work as you expect it to, because there's only one contract and one implementation. Do 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 only shares the "same name". So, it is a case of one contract and one implementation, when you call demo() with Gadget parameter, because compiler will match the method name with exact method parameter type(s) and by doing so will call "different methods" in both iterations of the loop.

Upvotes: 0

LhasaDad
LhasaDad

Reputation: 2133

Answering question 2 first: In the second loop your passing an object typed as a Gadget therefore the best match in the demo class is the method taking a gadget. this is resolved a compile time.

for question 1: the annotation does not make a difference. it just indicates that you overriding (implementing) method in the interface.

Upvotes: 0

morgano
morgano

Reputation: 17422

From the compilers point of view, what is the origin of the switchon method in Smartphone? Is it inherited from the base class Gadget? Or is it an implementation of the switchon method mandated by the switchonable interface?

The second case

Does the annotation make any difference here?

Not at all, @Override is just a helper, whe you use it you are telling the compiler: "my intention is to override the method from a supertype, please throw an exception and don't compile this if it is not overriding anything"

About the second question, in this case the method that better match acording to its signature is the one to be called. At run time in the second loop your objects have the supertype "associated", that's the reason public void demo(Gadget g) will be called rather than public void demo(Smartphone g)

Upvotes: 1

Ari Elfenbein
Ari Elfenbein

Reputation: 21

1.It shouldn't matter. Because it is extending Gadget, if you don't override and call switchon() from a smartphone, it would say "Gadget is switching on!". When you have both an interface and a parent class with the same method, it really doesn't matter.

2.The first loop works and the second doesn't because of the way java looks at objects. When you call a method from an object, it takes the method directly from that object, and thus knows whether smartphone or gadget. When you send either a Smartphone or Gadget into an overloaded method, everything in that class is called a Gadget, whether it is actually a smartphone or not. Because of this, it uses the gadget method. To make this work, you would want to use this in the demo(Gadget g) method of DemoPersonnel:

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 a tutorial, I learned through a combination of AP Computer Science and experience.

Upvotes: 0

Related Questions