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:
In this tutorial, we’ll pretty quickly explore both of these.
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.
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:
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.
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.
When overriding a method in a subclass, the method in the subclass must follow below rules:
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.
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
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!