In this article, we’ll talk about what polymorphism is in Java and how you can apply it in your own applications.
1. What Is Polymorphism in Java?
Polymorphism (Πολυμορφισμός in Greek) comes from the Greek word “poly” (πολύ) which means many, and “morphs”(μορφές), which means forms. So should we take the whole word, it means many forms.
Polymorphism in Java is one of the core OOP (Object Oriented Programming) concepts and it means to have many forms of the same; it can be split into two types:
- Compile-time polymorphism.
- Run-time polymorphism.
Compile-time polymorphism is when the method that is to be called is decided on compile time. We can achieve compile-time polymorphism through method, and constructor overloading.
Run-time polymorphism is when the method that is to be called, or the field to be used is decided on run-time (also known as late binding). We can achieve run-time polymorphism through method and field overriding.
2. Compile-Time Polymorphism
As we stated in the previous section, the compile-time polymorphism is applied through method overloading.
Before we start, what is method overloading?
Method overloading is when we define multiple methods with the same name but with a different signature. Different signature means that either the type of the parameters and/or the number of the parameters is different between them.
Note that changing the return type is not enough to differentiate two methods.
To demonstrate this, we have created a simple class Person
with the following attributes:
String name
String surname
int age
2.1 Constructor Polymorphism Through Overloading
public Person() {} public Person(String name, String surname) { this.name = name; this.surname = surname; } public Person(String name, String surname, int age) { this.name = name; this.surname = surname; this.age = age; }
As you can observe in the code above, we defined 3 constructors: an empty constructor, a constructor with 2 arguments and one with three, and all of them have the same name.
Now we can create objects using whichever constructor suits our needs.
Person unknown = new Person(); Person john = new Person("John", "Smith"); Person jane = new Person("Jane", "Doe", 25);
All of the commands above will compile and run without any problem.
2.2 Method Polymorphism Through Method Overloading
private void sayHello(String name){ System.out.println("Hello, I'm " + name); } private void sayHello(String name, int age){ System.out.println("Hello, I'm " + name + ", and I'm " + age + "year(s) old."); }
Here we defined two methods with the same name, the first takes only the name
as an argument, and the second the name
and the age
.
However, we could not add the following method:
private void sayHello(String surname){ System.out.println("Hello, I'm " + surname); }
As we already have a sayHello(String)
and the name of the parameter is not enough to differentiate the two methods.
In addition, neither can we add this method:
private String sayHello(String name){ return "Hello, I'm " + name; }
As we have the same problem as before since the return type is not enough to differentiate the two methods.
So taking into account the objects created in the previous section, we can call the 2 methods we defined as shown below:
john.sayHello("John", 30); jane.sayHello("Jane");
and this snippet will output:
Hello, I'm John, and I'm 30year(s) old. Hello, I'm Jane
3. Run-Time Polymorphism
As we stated in the previous section, the runtime polymorphism in Java is applied through method overriding.
But before we start, what is method overriding?
Method overriding is when we define multiple methods with the same name, same signature, and return type in order to “ignore” the previously defined methods in a parent class or interface.
Overriding is not limited to methods as it can also be applied to attributes of a class.
Nevertheless, overriding is not possible when dealing with:
- constructors
- final methods
- static methods
3.1 Overriding a Class
The most common use-case of overriding is when we want to change the implementation of a method in a child class.
Let’s jump to a simple polymorphism example in java with 2 classes:
public class Car { protected final int maxSpeed = 200; protected void playSound(){ System.out.println("Playing car.mp3..."); } }
and
public class FastCar extends Car { protected final int speed = 350; @Override protected void playSound(){ System.out.println("Playing fastCar.mp3..."); }
As you can observe, we have one parent class named Car
, and one child class named FastCar
.
Since FastCar
class extends Car
class, if we did not override the playSound()
, the result when running this:
Car fastCar = new FastCar(); fastCar.playSound();
would be
Playing car.mp3...
But since we overrode this method the result will be:
Playing fastCar.mp3...
The reason “Playing fastCar.mp3…” gets printed is that the reference of the object dictates which method should be called and not the type of the object.
Additionally, an important note here is that we use the @Override
annotation in order for the compiler to know that we intend to override this method. If we had forgotten the extends Car
, it would produce the following error:
java: method does not override or implement a method from a supertype
Finally, in order to override a member of a parent class, the previous initialization won’t work since:
Car fastCar = new FastCar(); System.out.println(fastCar.maxSpeed);
will print 200, which has been assigned as the value in the parent class.
To make this work we have to initialize the fastCar
as follows:
FastCar fastCar = new FastCar(); System.out.println(fastCar.maxSpeed);
and this print 350.
3.2 Overriding an Abstract Class
Let’s say we have the 2 classes as in the previous example but with some major differences, the Car
class now is abstract and the playsound()
method is now declared as abstract, therefore it has no implementation.
public abstract class Car { public final int maxSpeed = 200; abstract protected void playSound(); }
Now let’s see how can we implement the FastCar
which should extend the class Car
.
We have two options:
Option 1:
We can have exactly the same class as before and it will compile without any problem:
public class FastCar extends Car { protected final int maxSpeed = 350; @Override protected void playSound(){ System.out.println("Playing fastCar.mp3..."); }
What changes here is that we are obliged to implement the playSound()
method, otherwise the code wouldn’t compile. If we don’t want to implement it, we should choose option two.
Option 2:
The only way to make the code compile and not implement the playSound()
is to declare the class FastCar
as abstract
and the playSound()
method abstract
, as shown below:
public abstract class FastCar extends Car { protected final int maxSpeed = 350; protected abstract void playSound(); }
Of course, this doesn’t add any value as we already inherit the signature of this method from the Car
class and we could omit adding protected abstract void playSound();
.
However, it would be useful if for any reason we wouldn’t want to implement the playSound()
method here but in a subclass of FastCar
.
3.3 Overriding Methods of Interface
If you do not know what an interface is, we suggest going through Java Interface Tutorial first.
For this example, we have created an interface named Drawable
as shown below:
public interface Drawable { void printAreaFormula(); void printName(); }
This interface has 2 methods, printAreaFormula()
and printName()
, which are effectively abstract like playSound()
in the previous example.
This interface is implemented by two classes in which we are obliged to implement the methods inside Drawable
if we do not want to declare them as abstract
.
class Square
:
public class Square implements Drawable{ @Override public void printAreaFormula() { System.out.println("Length * Height"); } @Override public void printName() { System.out.println("This is a square"); } }
class Circle
:
public class Circle implements Drawable{ @Override public void printAreaFormula() { System.out.println("π * Radius ^ 2"); } @Override public void printName() { System.out.println("This is a circle"); } }
Now to demonstrate how polymorphism works with interfaces, let’s go through two examples.
Drawable square = new Square(); square.printName(); square.printAreaFormula();
This will print:
This is a square Length * Height
Let’s see what happened here:
We create a new Square object with Drawable
as type, so when we call the printName()
or printAreaFormula()
, the decision of which implementation of Drawable
interface should be used is taken in run-time.
So if we try running the following code:
Drawable circle = new Circle(); circle.printName(); circle.printAreaFormula();
The result to be printed will be different:
This is a circle π * Radius ^ 2
3.4 Cases in Which Overriding Is Impossible
There are some cases for which we cannot apply run-time polymorphism.
3.4.1 Constructors
Java does not support overriding when it comes to constructors. However, you can use the constructor of a parent class in a child class with the keyword super
. To learn more about how constructors work, click here.
3.4.2 Final Classes and Methods
The keyword final
has a different meaning depending on where you use it.
Should you use it in a class, it means that this class cannot be extended by another class. Hence, it would be impossible to override the methods of a final class as you wouldn’t be allowed to even create a class that extends this final class. The use-case of this is to prevent anyone that will try to extend a class that you have declared as final.
If you decide to declare a method as final, it means that this method cannot be overridden. Therefore, you wouldn’t be able to achieve run-time polymorphism by overriding this method. The use-case of this is to prevent anyone from overriding the implementation of a method, as it could lead to unwanted results.
3.4.3 Static Methods
When it comes to static methods, you cannot override them in a child class. This applies to static methods declared either in a class, abstract class, or interface.
However, unlike when using the final
keyword you can extend static classes and override their non-static methods without any problem.
4. Conclusion
By now, you should be able to answer questions like “What is polymorphism in java?”, “How to achieve runtime polymorphism in java?” and “How does polymorphism work in java?” without any issue. You can find the source code on our GitHub page.