Let’s Do It PL

What is “Polymorphism” and what are the advantages of it?

Object-Oriented Programming provides many advantages to developers. Like other principles of Object-Oriented Programming, the Polymorphism principle helps us in many ways.

M. Hamdi Ozdil
Let’s Do It PL
Published in
7 min readFeb 14, 2021

--

In real life, a person can be many things at the same time. For example, an employee also can be a student and a mother or father. So we can say, he/she has 3 forms at the same time. In the dictionary, “Polymorphism” word means having the ability to have more than one form. We can say humans are polymorphic creatures when considered this point of view.

In Object-Oriented Programming, objects are also polymorphic. We create objects from classes and these objects act on behalf of their classes. So how can we gain many different forms? We remember inheriting other classes’ methods and variables by the Inheritance principle and also we achieve an IS-A relationship between objects with that. Suppose, we have an Animal class and a Cat class that extends it. Or let’s create together:

public class Animal{

public void sleep(){
System.out.println("Zzz...");
}

public void walk(int duration){
System.out.println(duration + " minutes walked today");
}
}

public class Cat extends Animal{

public void makingSound(){
System.out.println("Meow meow");
}
}

In this simple Inheritance example, we have a Cat IS-A Animal relationship above. So we can say, by being a Cat and an Animal at the same time, the Cat object has 2 forms. That’s what we want to achieve, right? So;

Polymorphism is the capability of objects to act in many different forms and can be simply gained by Inheritance.

Polymorphism vs Inheritance

Question is, then why we need another principle in a different name if we achieve that by using Inheritance. We could explain all these details below the title of Inheritance, right? Firstly, we need to know the difference between these principles.

Polymorphism and Inheritance have different aspects from each other.

  • Inheritance explains the action of inheriting another class.
  • Polymorphism explains what objects gain if objects have a superclass.

Polymorphism is so related to Inheritance so to understand better we should learn Inheritance first.

Polymorphism has 2 different types: Static (Compile-time) Polymorphism and Dynamic (Runtime) Polymorphism. Let’s explain them.

Static (Compile-time) Polymorphism

This is a polymorphism that is resolved during compile time. We achieve “Method Overloading” with this kind of polymorphism.

Method Overloading

Method Overloading lets us have many methods in the same name but different actions in the same class. Method Overloading gives a lot of advantages to developers. Let’s see this in action:

class Calculator{
public int sum(int a, int b){
return a+b;
}

public int sum(int a, int b, int c){
return a+b+c;
}
}

We have 2 methods named sum() but they have a small but important difference: Their parameter numbers. Despite having the same name, they are different methods thanks to Method Overloading. So we can have many different methods inside the same class but we can make them different actions when then called.

class Calculator{
public double multiplication(double a, int b){
return a*b;
}

public double multiplication(double a, double b){
return a*b;
}
}

We can also achieve Method Overloading by having the same number of parameters but this time their data types must be different in some way like above. Otherwise, we get an error immediately. Let’s check another example.

class Calculator{
public int sum(int a, int b){
return a+b;
}
public double sum(int a, int b){
return a+b;
} //throws error
}

This time we have 2 methods with the same name and the same parameters (both number and type) but have a different return type. That is not allowed. Overloading a method can be achieved by manipulating the parameter part of the methods, not manipulating the return type. It will show us an error like “sum(int, int) already defined in the same class” and it won’t compile.

So, an infinite amount of method with the same name with any kind of return type can be created if

  • their number of parameters are different or
  • their parameters’ types have differences.

Dynamic (Runtime) Polymorphism

This is a polymorphism that is resolved during runtime. We achieve “Method Overriding” with this kind of polymorphism. Method Overriding is also mentioned in the Abstraction principle. Let’s check the definition.

Method Overriding

Method Overriding is providing different implementation with the same-named method in the superclass. Let’s explain this in an example.

public class Animal{

public void makingSound(){
System.out.println("This animal making a sound.");
}
}

public class Cat extends Animal{

public void makingSound(){
System.out.println("Meow meow");
}
}

As we can see Cat class extends Animal class and they both have the makingSound() method in them. As we all know from Inheritance, Cat inherits methods of Animal class and also can use them. But in this case Cat object will have 2 makingSound() methods with the same parameter. We just learned that it is not allowed Method Overloading rules. Then what happens if we create a Cat object and call the makingSound() method?

Cat tom = new Cat();
tom.makingSound(); //output: Meow meow

As we see, we called the makingSound() method in the Cat class instead of the Animal class’s method. That means we override that method.

The main advantage of this action is that the class can give its own specific implementation to an inherited method and we did that without modifying the parent class code. It is very life saving when we have objects that have lots of parent-child relationships.

Are parents allowed to use their child’s properties in the real world? Yes, right? If the child allows that of course :) But in our examples, only children can use their parent’s properties. What if we want to the same thing in our code world? In this case, Upcasting kicks in.

Upcasting

Upcasting is casting an object from a subclass to a superclass. Let’s use the same Animal and Cat class example above and create some objects.

public class Animal{
String species;
public void makingSound(){
System.out.println("This animal making a sound.");
}
}

public class Cat extends Animal{
String color;
public void makingSound(){
System.out.println("Meow meow");
}
}

The next step is creating an object of the Cat class.

Cat cat = new Cat();

As far as we know, Cat IS-A Animal. In other words, we can say every Cat object is an Animal object. Then what about this:

Animal animal = cat;        //implicit way
//animal = (Animal) cat; //explicit way

Is this code works? It works perfectly because we already created this relationship before. This is upcasting. The explicit way is not preferred that much. We do not need to create a subclass object first and reference it later. We can directly cast like this:

Animal animal = new Cat();

If we call the makingSound() method for both objects:

animal.makingSound();
cat.makingSound();

/*
Output:
The cat meows.
The cat meows. */

Normally, if the animal object is cast as an Animal, we expect “This animal makes a sound.” output but we used the method that overrides thanks upcasting.

We must not think like it is Inheritance, we are not extending Cat class. If we want to use other fields that don’t override, it will throw an error.

animal.species = "Mammals";
//animal.color = "Black and White"; //it throws an error

The Animal class has the “species” variable itself but the “color” variable belongs to the Cat class. Upcasting doesn’t give access to a subclass’s properties but Downcasting does.

Downcasting

Downcasting is casting from a superclass to a subclass, the opposite of Upcasting. We are using Downcasting when we want to invoke a method that only available in the subclass. Let’s see this in the example:

public class Animal{
public void makingSound(){
System.out.println("This animal making sound.");
}
}

public class Cat extends Animal{
public void meow(){
System.out.println("Meow meow");
}
}

And of course, we need an object. Let’s create:

Animal animal = new Cat();

The meow() method belongs to the Cat class. If the animal object calls the meow() method it will throw an error in runtime. Because our object doesn’t have that method. If we still want to do that, that means we need a downcast here:

//animal.meow();         //Throws a runtime error
((Cat) animal).meow(); //Downcasting
//Output: Meow meow

The first line is commented out because it will throw an error in runtime as we explain above. The second line will work perfectly and lets the animal object invoke the meow() method. Inner parenthesis for Type Casting (we call that “cast operator”) and outer parenthesis covering casting for compiling. Otherwise, it will most likely throw an error.

Rules of Method Overriding

  1. Access Modifiers: Overriding method’s access modifier should be less restrictive than the overridden method of the superclass. Let’s say we have a public method and if we want to override it must be public because any other access modifier (protected, default, private) is more restrictive than public.
  2. Parameter List: Overriding method and overridden method must have the exact same parameters.
  3. Encapsulation: The methods that are declared as private, final, or static cannot be overridden. These methods can be used only by their classes so overriding them is not allowed.
  4. Exceptions: Overriding method can throw an “Unchecked Exception” but cannot throw a “Checked Exception”. Checked Exceptions are exceptions that are checked at compile time. Unchecked Exceptions are exceptions that are checked at runtime. Method Overriding is resolved at runtime so they are explained in Runtime Polymorphism. So compile time exceptions don't allow Method Overriding but runtime exceptions do.
  5. Abstraction: If we extend an interface or abstract class, we must override all of their methods due to the Abstraction principle.

Conclusion

Polymorphism is another big pillar of Object-Oriented Programming which allows us to use Method Overloading and Method Overriding. Thanks to this principle we gain reusable and clean codes as we aimed in the OOP concept. Polymorphism is highly related to other OOP principles (Encapsulation, Inheritance, Abstraction) so revisiting them will help to understand this principle better.

--

--

M. Hamdi Ozdil
Let’s Do It PL

Passionate with coding, In love with learning, ambitious to implementing them to his life.