Chapter 5 - Java for Beginners Course

Polymorphism

Another OOP concept that is important to understand is Polymorphism, or more precisely in Java subtype polymorphism as it is present when we have inheritance/subtype relationships.

To introduce this concept we’ll use slight variations of the Light and DimmableLight classes we used in a previous section:

public class Light {

    public void turnOn() {
        System.out.println("Turned the Light on");
    }

    public void turnOff() {
        System.out.println("Turned the Light off");
    }

}
public class DimmableLight extends Light {

    private int power;

    public DimmableLight(int power) {
        this.power = power;
    }

    @Override
    public void turnOn() {
        System.out.println("Turned on the dimmable light and setting power to: " + power);
    }

    @Override
    public void turnOff() {
        System.out.println("Turned off the dimmable light");
    }

    public void setPower(int power) {
        this.power = power;
    }

}

Similar to the previous example, we’ve defined two classes, a DimmableLight class that extends from a Light base class. In our DimmableLight class we’re also overriding both methods from the base class, turnOn and turnOff.

What do we mean by Polymorphism in Java?

Polymorphism refers to the possibility of an object reference to refer to objects of different types, or seen from a different perspective, the object can take a different form depending on the type of reference pointing to it.

In Java, we’ve already mentioned that this is achievable via subtyping/inheritance.

So far, in all our previous examples, our object references and the objects we create have always been of the same type, for example:

In the example code run the JavaPolymorphismApp
Light normalLight = new Light();

We know this is valid as the normalLight reference is of type Light and it is pointing to the Light object that is being created. Both, the reference and the object are of the same type so Java sees this as a valid assignment.

Now, let’s see what happens if we mismatch the types:

Light dimmableLight = new DimmableLight(25);
dimmableLight.turnOff();

Output:

Turned off the dimmable light

The code above is valid and this is down to the support in Java of subtype polymorphism. Our DimmableLight object is being referenced by an object reference of type Light.

It is important to note 3 points here:

  1. Keep in mind that in these cases the object that has been created is of the type you provided in the new statement, in our case of DimmableLight - even though the reference is of a different type (this sometimes causes confusion when learning the language).

  2. You’ll notice that the turnOff method that was executed is actually the one from our DimmableLight object as it overrides the one in the Light class. Hence, the message that is printed out is "Turned off the dimmable light". This is called virtual method invocation, Java calls the method for the object and not for the reference type.

  3. Now, even though our object is of type DimmableLight, because the reference we’re using is of type Light, the Java compiler will not let us invoke any methods that are not defined in the Light class.

To clarify the last point, let’s try invoking the setPower method:

dimmableLight.setPower(10);

In this case, the Java compiler gives us an error stating that the setPower method isn’t defined in the Light class. Basically, our DimmableLight object has taken the form of a Light object as this is the reference being used to access it.

Invalid usages

Polymorphism in Java allows us to substitute/use an object reference of a supertype to point/refer to an object of the same type or any of its subtypes, like in the examples above Light → Light or Light → DimmableLight.

However, we can’t do the following:

  • We can’t use a subtype to refer to an object of a supertype, for example:

DimmableLight invalidRef = new Light();
  • Nor we can use types that aren’t related to one another, for example:

Light invalidRef = new Airplane();

Reason for this is that the reference type being used has methods/state that aren’t defined in the object they refer to. If the example above were valid, it would mean we could invoke invalidRef.turnOn() and our Airplane objects don’t have such method.

Hence why this is called subtype polymorphism as it is only valid from supertype reference to subtype objects.

Is that it?

One of the first feelings with polymorphism is that this might not add a lot of value. Basically, we’re saying that we can have an object and use an object reference of a different type (as long as it’s a supertype), like:
Light light = new DimmableLight(10).

When looking into software design principles, polymorphism is a very powerful tool that allows developers to build classes/components that aren’t coupled to specific types. Let’s look at an example below.

We won’t go into too many details in this course, but we wanted to note that as simple as this concept is, it becomes of more importance in software design.

Example with method invocations

Let’s assume we have the following class that is used to handle the lights we have in our system:

public class LightHandler {

    public void turnOn(Light light) {
        light.turnOn();
    }

    public void turnOff(Light light) {
        light.turnOff();
    }

}

The LightHandler class has two methods that receive a Light object as a parameter. If polymorphism weren’t a feature in Java, our LightHandler would have to define turnOn(…​)/turnOff(…​) methods for each type of light we define in our system, for example, turnOn(Light light) and turnOn(DimmableLight light) (and others if we defined more subclasses of Light).

However, given that we have polymorphism supported in Java, we can use objects from subtypes of Light and pass them to our LightHandler methods.

For example:

Light normalLight = new Light();
Light dimmableLight = new DimmableLight(25);

LightHandler handler = new LightHandler();
handler.turnOn(normalLight);
handler.turnOn(dimmableLight);

Output:

Turned the Light on
Turned on the dimmable light and setting power to: 25

Although not the focus in the beginners course, the above example starts showing that we can design classes like LightHandler that are meant to support multiple implementations of a Light contract. The LightHandler doesn’t need to know about all the different subtypes/implementations of Light to do its work; all it needs to know is that Light objects have a turnOn and a turnOff method.

This relates to 2 concepts, the "Liskov Substitution Principle" and "Design by Contract" which are important concepts in software design not covered in this course. This also relates to Interfaces which we’ll cover in this chapter.