Java - Polymorphism

Subscribe Send me a message home page



In this post, we will go through one classic example that illustrate the edge cases of polymorphism in Java.

Example: What will happen if we call a polymorphic function in the constructor.
public class Base {
    public Base() {
        System.out.println("Base() is called.");
        f();
    }

    public void f() {
        System.out.println("Base.f() is called.");
    }
}


public class Derived extends Base {
    public Derived() {
        System.out.println("Derived() is called.");
        f();
    }

    public void f() {
        System.out.println("Derived.f() is called.");
    }
}


public class Main {
    public static void main(String[] args) {
        System.out.println("main(): create a Derived instance.");
        Base derivedInstance = new Derived();

        System.out.println("\nmain(): create a Base instance.");
        Base baseInstance = new Base();
    }
}
Here is the output of the above program.
main(): create a Derived instance.
Base() is called.
Derived.f() is called.
Derived() is called.
Derived.f() is called.

main(): create a Base instance.
Base() is called.
Base.f() is called.
This may or may not be surprising depending on how we interpret it. From the polymorphism perspective, it is working because the instance is derived class when the red line output is generated. On the other hand, it is not good because we are calling a method of derived class within the constructor of the base class.

In order to "fix" the problem (if we consider this as a problem), we can change the method to a private method or a static method. Making the method final can also help in this situation because in that way, we cannot even have the function f() defined in the derived class.

Here is the code after the changes.
public class Base {
    public Base() {
        System.out.println("Base() is called.");
        f();
        g();
    }

    private void f() {
        System.out.println("Base.f() is called.");
    }

    public static void g() {
        System.out.println("Base.g() is called.");
    }
}


public class Derived extends Base {
    public Derived() {
        System.out.println("Derived() is called.");
        f();
        g();

    }

    public void f() {
        System.out.println("Derived.f() is called.");
    }

    public static void g() {
        System.out.println("Derived.g() is called.");
    }
}


public class Main {
    public static void main(String[] args) {
        System.out.println("main(): create a Derived instance.");
        Base derivedInstance = new Derived();

        System.out.println("\nmain(): create a Base instance.");
        Base baseInstance = new Base();
    }
}

And here is the new output of the program:
main(): create a Derived instance.
Base() is called.
Base.f() is called.
Base.g() is called.
Derived() is called.
Derived.f() is called.
Derived.g() is called.

main(): create a Base instance.
Base() is called.
Base.f() is called.
Base.g() is called.

The reason why we get these outputs is the following: all the methods in Java are late binding except static and final methods. In Java, the private methods are implicitly final and it is hidden from the derived class.

In general, when we call a method, the compiler needs to know which instance and which type this method is associated with. This process is called binding. We may have two different binding types: (1) static binding or early binding. This type of binding occurs at compile time. (2) dynamic binding or late binding. This type of binding occurs at run time and it is this type of binding that make the polymorphism work.

Therefore in the first version of the code, the call of the method f() in the constructor of Base class is late bind, which means at compile we do not know which version to call and we can only get that information at run time. In our example, we realize that the instance is of Derived type at run time and f() is bind to the method of Derived class.

In the second version of the code, the call of the method f() in the constructor of Base class is early bind, which means at compile time we know the function defined in the Base class should be called at run time.

----- END -----

Send me a message Subscribe to blog updates

Want some fun stuff?

/static/shopping_demo.png


Comments