This post was originally published on this site

Java is an Object-Oriented Programming (OOP) language that is especially useful when you need to implement well-structured code and increase its reusability and maintainability.

OOP uses four principles to describe the relationships between classes: inheritance, encapsulation, abstraction, and polymorphism. There’s at least one class in each Java program. Therefore, it’s essential to understand the principles of OOP and how to use them in your applications. 

This article explores polymorphism in its various forms, how it relates to the other Java classes, and its use cases and best practices.

If you’re in a time crunch, use the links below to find exactly what you’re looking for:

What is polymorphism in Java?

Polymorphism, which literally means “different forms,” is one of the core concepts of OOP. Polymorphism explores how to create and use two methods with the same name to execute two different functionalities — like adding two functions with the same name but that accept different parameters.

For example, let’s explore how you can define two functions with the name Multiply(). One is used to calculate the product of two integers, and the other is used to calculate the product of two doubles.

 
public class Main {

    public static void main(String[] args) {

        // using the first method

        Multiplier.Multiply(3,5);

        // using the second method

        Multiplier.Multiply(3.5,5.1);

    }

}

When the code is run, the output is as follows:

Integer Multiplication, Result = 15

double Multiplication, Result = 17.849999999999998

You’ll notice that while calling the two methods, there’s no difference except for the parameters passed. Additionally, note that the output depends on the type of the passed parameters.

Differences Between Inheritance and Polymorphism

Understandably, developers sometimes confuse polymorphism with another core concept of OOP: inheritance. However, the two principles are substantially different.

In inheritance, a class that’s referred to as a child class can inherit the methods and attributes from another class (the parent class). See the following example:

 
class Shape {

// methods of Shape class

}

class Square extends Shape {

// methods of Shape class are not specified here but are automatically accessible to this Square class thanks to inheritance

// methods of Square class

}

In inheritance without polymorphism, the child class inherits the same attributes and methods of the parent class without any modifications to their functionality. With polymorphism, the child class inherits the attributes and methods, but provides its own implementation (code) for these methods.

Inheritance is a way to enable code reusability while polymorphism is a way to dynamically decide which version of a function will be invoked. For example, inheritance enables you to use the attributes and methods of a parent class. In contrast, polymorphism allows for a child class to define its version of a function with the same name as one in its parent class. Alternatively, it allows a function with the same name to be implemented in several different ways and will select one to execute based on the number and types of parameters given.

Polymorphism is also used to support the “Open-Closed” principle, which represents the “O” in the SOLID acronym. It states that code should be open for extension and closed for modification. In other words, when you try to add more functionality to your code, you shouldn’t modify the existing classes (closed for modification). Instead, you should extend the functionality by means of inheritance and polymorphism.

Polymorphism vs. Inheritance: An Example

If you have two kinds of cars — for example, BMW and Mercedes — you can create a Car class, then have a BMW class and a Mercedes class that each inherit from the Car class. This means they inherit its attributes and functions.

With polymorphism, you can assume that the Car (parent class) can be a BMW (child class) or a Mercedes (child class). Therefore, you can use the parent class, Car, to refer to either of the two types of cars.

 
public class Car{}

Public class BMW extends Car {}

Public class Mercedes extends Car{}

Car myCar = new BMW();

//…some code…

myCar = new Mercedes();

//…some code…

This code example defined a variable myCar of type Car and used it to hold a new object — BMW. Then, it used the same variable to refer to Mercedes, which is possible because they are both child classes of Car.

This is a simple example of upcasting, wherein the parent class refers to an object from the child class. By using upcasting, you can access the child version of the parent class. This means that you can access the methods that were defined solely in the parent class, but if a method with the same name is specified in the child class, you can access that one. Upcasting is useful when your application needs to determine which version of code to call during runtime.

Types and Examples of Polymorphism in Java

Java supports two types of polymorphism:

  • Compile-time polymorphism (static polymorphism)
  • Runtime polymorphism (dynamic polymorphism)

Compile-Time Polymorphism

Also called static polymorphism, this type of polymorphism is achieved by creating multiple methods with the same name in the same class, but with each one having different numbers of parameters or parameters of different data types.

In the example below, notice that the three methods have the same name, but each accepts a different number of parameters or different data types:

 
package com.hubspot;

class Multiplier {

    static void Multiply(int a, int b)

    {

        System.out.println("Integer Multiplication, Result = "+ a*b);

    }

    // Method 2

    static void Multiply(double a, double b)

    {

        System.out.println("double Multiplication, Result = "+ a*b);

    }

    // Method 3

    static void Multiply(double a, double b, double c)

    {

        System.out.println("Three parameters, double Multiplication, Result = "+ a*b*c);

    }

}

public class Main {

    public static void main(String[] args) {

        // using the first method

        Multiplier.Multiply(3,5);

        // using the second method

        Multiplier.Multiply(3.5,5.1);

        // using the third method

        Multiplier.Multiply(3.6,5.2, 6.3);

    }

}

When the code is run, the output is as follows:

Integer Multiplication, Result = 15

double Multiplication, Result = 17.849999999999998

Three parameters, double Multiplication, Result = 117.936

The first method takes two integers with different parameters, the second method takes two double parameters, and the third one takes three double parameters. Notice that we don’t specify any additional information while calling them. The three calls are the same except for the number or the parameter type.

Runtime Polymorphism

Also called dynamic polymorphism, this type of polymorphism occurs when a child class has its own definition of one of the member methods of the parent class. This is called method overriding. In most cases, runtime polymorphism is associated with upcasting. This is when a parent class points to an instance of the child’s class.

Here’s an example:

 
package com.hupspot;

// Super Class

class Shape{

    protected double length;

    Shape(double length){

        this.length = length;

    }

    void area(){

    }

}

// Child class

class Square extends Shape{

    //constructor

    Square(double side){

        super(side); // calling the super class constructor

    }

    //Overriding area() method

    void area(){

        System.out.println("Square Area = " + length*length);

    }

}

// Child class

class Circle extends Shape{

    //constructor

    Circle(double radius){

        super(radius); // calling the super class constructor

    }

    //Overriding area() method

    void area(){

        System.out.println("Circle Area = " + 3.14*length*length);;

    }

}

public class Main {

    public static void main(String[] args){

        Shape shape = new Square(5.0);

        // calling the area() method of the Square Class

        shape.area();

        shape =  new Circle(5.0); // upcasting

        // calling the area() method of the Circle Class

        shape.area();

    }

}

The output:

Square Area = 25.0

Circle Area = 75.0

The last example represents the SOLID principle’s “Open-Closed” principle. We extended the code with child classes and didn’t modify the existing Shape class.

Polymorphism Use Cases and Best Practices

One of the use cases of polymorphism is for using one method name to automatically invoke the correct method depending on the class. For example, in the use of a single storage element to store multiple types, the following example implements a Set of type Shape from the previous example and stores objects from different child classes like Circles and Squares:

 
Set<Shape> hs = new HashSet<Shape>();

hs.add(circle1);

hs.add(circle2);

hs.add(square1);

hs.add(square2);

for(Shape hs_element : hs){

      hs_element.area();

}

Another use case for polymorphism is to replace conditional statements in your code. For example, the code below uses a switch statement in the area method to determine which code to run when it is called, based on the first parameter passed to it. You’ll notice it achieves the same result as the runtime polymorphism code example above. However, this approach isn’t as straightforward.

 
package org.hubspot;

// Super Class

class Shape{

    enum Type {

        SQUARE,

        CIRCLE

    }

    protected double length;

    Type shape_type;

    Shape(Type shape, double length){

        this.length = length;

        this.shape_type = shape;

    }

    double area(){

        double area = 0;

        switch (shape_type)

        {

            case SQUARE:

                area =  length * length;

                break;

            case CIRCLE:

                area =  3.14*length * length;

                break;

        }

        return area;

    }

}

public class Main {

    public static void main(String[] args) {

        Shape Circle = new Shape(Shape.Type.CIRCLE, 10);

        System.out.println("Circle Area equals = "+ Circle.area());

        Shape Square = new Shape(Shape.Type.SQUARE, 10);

        System.out.println("Square Area equals = "+ Square.area());

    }

}

Using Polymorphism in Java

Polymorphism is a handy tool to have when developing with Java or any other Object-Oriented Language. This article explained what polymorphism is in Java and how to use it in your applications. It showed examples of polymorphism used like the Multiplier, class and the different area calculations for different shapes.

We have also identified some benefits of polymorphism, such as the ability it provides us to reuse code. Additionally, it enables using a single interface to handle different class types. It also adheres to the SOLID principle of OOP by extending classes instead of using the switch case. As a result, the application is open for extension and closed for modification.

Finally, Polymorphism is very useful when resolving the methods called either during the run-time or the compile time is needed.java