Polymorphism

Polymorphism literally means "Many Forms". In Object-Oriented Programming it can be described as the ability to present the same interface for different underlying data types (paxdiablo, 2009)

These different forms can be seen in both Method Overriding (where the implementation of a method in an inherited type can be varied) and Method Overloading (where a method with the same name can take parameters of varying types). It's important to understand the differences between these, so as a developer, you are know which method would be called in any given circumstance, as it is not always as you might expect

Method Overriding

With Method overriding, the method applicable to the subtype of the object will always be called, no matter how the object is declared. Consider the following three classes:

public class Appliance {
    String powerOn(){
        return "I'm consuming power";
    }
}
public class Toaster extends Appliance {
    @Override
    String powerOn() {
        return "I'm toasting";
    }
}
public class Dishwasher extends Appliance {
    @Override
    String powerOn() {
        return "I'm washing dishes";
    }
}

If we created a toaster as follows:

Toaster toaster = new Toaster();
and called the toaster.powerOn() method. The overridden method in Toaster would be called and I'm toasting would be returned

Similarly we could create a new dishwasher, and even declare it as its supertype, as follows:

Appliance dishwasher = new Dishwasher();
Calling dishwasher.powerOn() would still call the overridden method, so I'm washing dishes would be returned.

Overriding with collections

If we place the objects in a collection, such as an ArrayList<Appliance>, as shown in the code here:

ArrayList<Appliance> appliances = new ArrayList<Appliance>();
appliances.add(toaster);
appliances.add(dishwasher);
for (Appliance appliance: appliances) {
    //calls overridden methods
    System.out.println(appliance.powerOn()); //varies based on subtype
}
When we call methods on each appliance in turn, the overridden method is still called, so in the above code the output would be:
I'm toasting
I'm washing dishes

Method Overloading

When it comes to overloaded methods however, the type that an object is declared as determines which overloaded method is called. The following class provides descriptions for objects passed into the getDescription method, based on their type:

public class ApplianceDescriber {
    String getDescription (Appliance appliance) {
        return "This is a kitchen appliance";
    }
    String getDescription (Toaster toaster){
        return "This is a toaster";
    }
    String getDescription (Dishwasher dishwasher){
        return "This is a dishwasher";
    }
}
If we were to write the following code:
Toaster toaster = new Toaster();
String desc = describer.getDescription(toaster);
the desc variable would have the value: This is a toaster

On the other hand, if we were to declare a new instance of a toaster as an Appliance, and pass it to the getDescription() method as follows:

Appliance toaster = new Toaster();
String desc = describer.getDescription(toaster);
the desc variable would have the value: This is a kitchen appliance

The same principle applies, so that if we create a collection of objects which are passed as parameters into an overloaded method, then they will be treated as being of the type of the collection. In the following example, even though the ArrayList only contains items of the type Toaster, because the ArrayList is declared as being of the type Appliance, the overloaded method which takes a parameter of type Appliance would still be the one called:

ArrayList<Appliance> appliances = new ArrayList<Appliance>();
Toaster toaster = new Toaster();
appliances.add(toaster);
appliances.add(toaster);
for (Appliance appliance : appliances){
    System.out.println(describer.getDescription(appliance));
}
and the output would be as follows:
This is a kitchen appliance
This is a kitchen appliance