In broad terms, polymorphism gives us an option to use the same interface for entities of different types. There are two major types of polymorphism, compile time and runtime. Say you have a Shape class that has two area methods. One returns the area of a circle and it accepts single integer; that is, the radius is input and it returns the area. Another method calculates the area of a rectangle and takes two inputs, length and breadth. The compiler can decide, based on the number of arguments in the call, which area method is to be called. This is the compile-time type of polymorphism.
There is a group of techies who consider only runtime polymorphism as real polymorphism. Runtime polymorphism, also sometimes known as subtyping polymorphism, comes into play when a subclass inherits a superclass and overrides its methods. In this case, the compiler cannot decide whether the subclass implementation or superclass implementation will be finally executed, and hence a decision is taken at runtime.
To elaborate, let's take our previous example and add a new method to the vehicle type to print the type and name of the object:
public String toString()
{
return "Vehicle:"+name;
}
We override the same method in the derived Car class:
public String toString()
{
return "Car:"+name;
}
Now we can see subtyping polymorphism in action. We create one Vehicle object and one Car object. We assign each object to a Vehicle variable type because a Car is also a Vehicle. Then we invoke the toString method for each of the objects. For vehicle1, which is an instance of the Vehicle class, it will invoke the Vehicle.toString() class. For vehicle2, which is an instance of the Car class, the toString method of the Car class will be invoked:
Vehicle vehicle1 = new Vehicle("A Vehicle");
Vehicle vehicle2 = new Car("A Car")
System.out.println(vehicle1.toString());
System.out.println(vehicle2.toString());