Deep Dive into Polymorphism in Java

Deep Dive into Polymorphism in Java

In Java, Polymorphism is a prevalent concept of Object Oriented Programming. Let's take a closer look at how polymorphism works in Java.

Polymorphism as the name suggests means having different forms.

Poly = Many
Morph = Forms

We generally witness polymorphism when we have different classes related to each other by inheritance. In Java, Polymorphism is the ability of an object, variable, or function to take on many forms or behave differently depending on our requirements. Subclasses of a class can define their own unique behaviors and yet share some of the same functionality of the parent class. In simple words, we can say Polymorphism helps us achieve the same task in different ways.

One of the best real-time examples of polymorphism is a smartphone. It can act as a phone, camera, music player and what not, taking different forms and hence depicting polymorphism.

In Java, Polymorphism can be classified into two types:

  1. Compile Time Polymorphism (Static Polymorphism)
  2. Run-Time Polymorphism (Dynamic Polymorphism)

Compile Time Polymorphism or Static Polymorphism

It is a polymorphism that gets resolved during the compilation process. Here the object is bound to its functionality at the time of compilation. Compile time polymorphism can be achieved in two ways:

  1. Method Overloading
  2. Operator Overloading

Method Overloading

Java can distinguish between methods with different method signatures. What we mean by that is, Java can have methods with the same name and different parameters within a class. These methods could have different data types or different numbers of parameters or both. Let's get a clear understanding with the code snippet below:

//Method without any Parameters
void Multiply() { ... }

//Method with 3 parameters of same data type
void Multiply(int num1, int num2, int num3) { ... }

//Method with 2 parameters of same data type
void Multiply(float num1, float num2) { ... }

//Method with 3 parameters, all with different data types
void Multiply(int num1, float num2, double num3 ) { ... }

Example

class CalculateProduct{
    int Multiply(int a, int b) {
        return a*b;
    }
    int Multiply(int a, double b) {
        //Here we did type casting to get the result in integer format
        return (int) (a*b);
    }
    //Here we changed the data type and increased the number of parameters
    double Multiply(int a, float d, double c) {
        return a*d*c;
    }
}
public class MethodOverloading {

    public static void main(String[] args) {
        CalculateProduct cp = new CalculateProduct();
        System.out.println(cp.Multiply(10, 20));
        System.out.println(cp.Multiply(10, 3.0));
        //Here We casted the argument 20.00 as float to remove the ambiguity
        System.out.println(cp.Multiply(10, (float) 20.00, 80.00));

    }

}

Output

200
30
16000.0

In the above example, we have created a class namely CalculateProduct, which has three methods with same name Multiply. The first method has two parameters both integers. After giving integer type inputs 10 and 20, the output is 200.

The second method have two parameters with data type int and double respectively. The return value is type casted to remove the ambiguity and match with the return type of the method. The input is given in integer(10) and double(3.0) data type and we receive the output 30 in integer format.

The third case has three parameters all with different data types. The return type of the method is changed to double as one data type is promoted to another implicitly if no matching data type is found. In this case when there are three different data types available then all can get promoted to double data type. We can get a clear understanding of this concept with the help of the image given below:

java-type-promotion.png Image credit: JavaTPoint

Hence we get the output as 16000.0 which is in double format.

Note: Double can not be demoted to any data type implicitly.

Operator Overloading

Overloading in general means that there are several implementations for one function. It a process where an operator can perform multiple functionalities.

Java does not support user-defined operator overloading to keep the language more simple.

Although in Java the '+' operator has a built in feature where it can be used both as an arithmetic operator to add two numbers and also to concatenate two strings.

Let's understand with a code snippet:

Example

public class operatorOverloading {
    void add(int a, int b)
    {
        int sum = a + b;
        System.out.println("  Addition of two integer : " + sum);
    }
    void add(String s1, String s2, String s3, String s4)
    {
        String cstr = s1 + s2 + s3 + s4;
        System.out.println("  Concatenated strings :" + cstr);
    }

    public static void main(String[] args) {
        operatorOverloading obj = new operatorOverloading();

        // addition of two numbers
        obj.add(100, 10);

        // concatenation of four strings
        obj.add("Operator ", "Overloading ", "in ", "Java.");

    }

}

Output

Addition of two integer : 110
Concatenated strings :Operator Overloading in Java.

Compile Time Polymorphism provides clarity in the code and also helps in faster execution of the program.

Run Time Polymorphism or Dynamic Polymorphism

It is a polymorphism that gets resolved during runtime. Here the object is bound to its functionality at the run time. Run time polymorphism can be achieved by method overriding.

Method Overriding

When we declare a method in the sub class which is already present in the super class it is called method overriding. The body of the method can be changed while implementing it in the sub class. It is done so that the sub class can give its own implementation to a method which is already provided by the super class. In this case, the method in super class is called overridden method, and the method in sub class is called overriding method.

Let's get a better idea with an example:

Example

class Car{  
int avgSpeed(){return 0;}  
}  
//Creating child classes.  
class Toyota extends Car{  
int avgSpeed(){return 18;}  
}  

class Hyundai extends Car{  
int avgSpeed(){return 15;}  
}  
class Scorpio extends Car{  
int avgSpeed(){return 12;}  
}  
public class methodOverriding {

    public static void main(String[] args) {
        Toyota t=new Toyota();  
        Hyundai i=new Hyundai();  
        Scorpio s=new Scorpio();  
        //Super Class Refers to Sub Class's object
        Car v=new Toyota(); 
        //Super Class refers to super class's object
        Car c = new Car();
        System.out.println("Average Speed Of Toyota: "+t.avgSpeed());  
        System.out.println("Average Speed Of Hyundai: "+i.avgSpeed());  
        System.out.println("Average Speed Of Scorpio: "+s.avgSpeed());  
        System.out.println("Average Speed Of Toyota : "+v.avgSpeed());  
        System.out.println("Average Speed Of Car : "+c.avgSpeed());  

    }

}

Output

Average Speed Of Toyota: 18
Average Speed Of Hyundai: 15
Average Speed Of Scorpio: 12
Average Speed Of Toyota : 18
Average Speed Of Car : 0

In the above example, we can observe that the method of super-class Car is overridden by its sub classes and the objects created are bound to their functionalities at run time. Also when Super Class refers to Sub Class's objects then the method of Sub Class is called. When Super Class refers to its own object then the method of Super Class is called.

Note:

  1. Static Methods can not be overridden.
  2. Final methods can not be overridden.
  3. Private methods can not be overridden.

So these were the ways by which both run time and compile time polymorphism can be implemented. Also, we got a good idea of what is polymorphism in general and how can it be applied in real-life scenarios. I hope you liked the explanation and found this article informative.

Have a great time! Please write comments if you find anything incorrect and remember to like and share.

Did you find this article valuable?

Support Ishita Ghosh by becoming a sponsor. Any amount is appreciated!