Core Java

Polymorphism In Java

Introduction:

Polymorphism in Java provides us an ability by which an object can take on different forms based on our usage context. Polymorphism is derived from two words – “poly” which means “many” and “morphs” which means “forms”.

Java defines two major types of polymorphism:

  • Compile-time / Static Polymorphism also referred to as “Method overloading”
  • Runtime / Dynamic Polymorphism, also known as “Method overriding”

In this tutorial, we’ll pretty quickly explore both of these.

Compile-time/Static Polymorphism (Method Overloading):

Compile-time or static polymorphism is otherwise popularly known as ‘Method Overloading’. It helps us define multiple methods with the same name but accepting different arguments:

public class MathUtil {

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

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

For instance, the above two sum() methods are the overloaded versions of each other. Let’s invoke the above methods:

MathUtil myUtil = new MathUtil();
int sumOfTwo = myUtil.sum(2, 3);
int sumOfThree = myUtil.sum(2, 3, 4);

Clearly, the method which will be invoked is decided at the compile phase itself. Therefore, we refer to it as a compile-time or static polymorphism.

Rules for Method Overloading:

In compile-time polymorphism or method overloading, an object can have two or more methods with the same name, BUT the must have different method arguments. Their method arguments must differ in at least one of the following ways:

  • Number of arguments
  • Types of arguments
  • Order of method arguments

Let’s practice out a few examples to see if we can identify a valid overload:

public int sum(int x, int y) { //invalid - fails to compile
    return x + y;
}

public int sum(int a, double b) { //valid
    return (int)(a + b);
}

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

The method sum(int x, int y) is an invalid overload of our existing sum(int a, int b) method as it only changes the variable names.

Runtime/ Dynamic Polymorphism (Method Overriding):

To understand dynamic polymorphism, we must have a good knowledge of Inheritance in Java. Runtime polymorphism is a process by which the call to the overridden method is resolved at runtime (not during code compilation). It is otherwise also known as a Dynamic Method Dispatch.

Let’s consider the below example:

class Vehicle {    
    public void drive() {
        System.out.println("Driving a vehicle...");
    }
}

class Car extends Vehicle {
    @Override
    public void drive() {
        System.out.println("Driving a car...");
    }
}

Clearly, our Car IS-A Vehicle. So, when we use the reference of a superclass to refer to the object of any of its subclasses, something like:

//Code in our main() method
Vehicle vehicle = new Car();
vehicle.drive(); // Prints "Driving a car..."

There’s no way for the Java compiler to know which drive() method to invoke during compilation. This is so as the objects are actually created in the heap memory only at runtime. When we execute our code, JVM gets to know that the reference variable – vehicle actually refers to a Car instance. Our Car defines its own drive() implementation i.e. it overrides drive() method from the Vehicle class. So, our code will print “Driving a car…” as an output to the console.

Rules for Method Overriding:

When overriding a method in a subclass, the method in the subclass must follow below rules:

  1. It must have the same method signature as the method in its superclass
  2. Should have either the same or at least a co-variant return type
  3. The overriding method must have either the same or a less restrictive access modifier
  4. It can’t throw any new or broader checked exception

Having a co-variant return type simply means that if the overridden method returns an object of type T, then the overriding method can return either an object of type T or any subclasses of type T. Let’s remember that it isn’t applicable for primitive return-types.

Data Members aren’t Overridden:

It’s important for us to note that overriding is only applicable to methods. We can’t achieve runtime polymorphism for member variables. For instance, let’s consider:

class Vehicle {
    boolean isAlwaysFourWheeler = false;
}

class Car extends Vehicle {
    boolean isAlwaysFourWheeler = true; //hiding, not overriding
}

//In our main() method
Vehicle vehicle1 = new Car();
System.out.println(vehicle1.isAlwaysFourWheeler);
Vehicle vehicle2 = new Vehicle();
System.out.println(vehicle2.isAlwaysFourWheeler);

A data member isAlwaysFourWheeler will always have its value determined based on the reference type, not the object type. So on executing our code, we’ll get the below output:

false
false

Conclusion:

In this article, we learned about two major types of polymorphism in Java – static and dynamic polymorphism. By now, we’re at least in a position to identify valid and invalid method overloads and overrides.

Hope you find this article useful!

Be the First to comment.

Leave a Comment

Your email address will not be published. Required fields are marked *