How does Java Implement run time Polymorphism

AlishaS
Javarevisited
Published in
8 min readMar 17, 2023

--

Introduction

Polymorphism is a core concept in object-oriented programming that refers to the ability of an object to take on multiple forms. In Java, polymorphism allows objects of different classes to be treated as objects of the same class.

This enables the program to perform a single action in multiple ways, depending on the type of object to which it is applied. Polymorphism is an important concept in object-oriented programming because it promotes code reusability, reduces complexity, and increases flexibility.

In this article, we will discuss how Java performs run time polymorphism, as well as the concept of interfaces and abstract methods.

What is Polymorphism?

Polymorphism is composed of two words ‘poly’ means many and ‘morph’ means forms. It is an object-oriented programming (OOP) concept that refers to the ability of an object to take on multiple forms. This means that objects of different classes can be treated as if they were objects of a common class, allowing for more general and reusable code to be written.

There are two main forms of polymorphism in Java: compile-time polymorphism and run-time polymorphism. Compile-time polymorphism is also known as method overloading and occurs when multiple methods with the same name are defined, but with different parameters. Run-time polymorphism is also known as method overriding and occurs when a subclass provides a different implementation of a method that is already defined in its superclass.

Here’s an example to demonstrate how polymorphism works in Java:

Let’s say you have a base class called Animal with a method called makeSound(). This method is implemented in each of the subclasses of Animal (e.g., Cat, Dog, and Cow), but with a different implementation to produce a unique sound for each animal.

class Animal {
public void makeSound() {
System.out.println("Animal Sound");
}
}

class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Meow");
}
}

class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Woof");
}
}

class Cow extends Animal {
@Override
public void makeSound() {
System.out.println("Moo");
}
}

Now, let's say you have an array of Animal objects, which can contain instances of a Cat, Dog, or Cow:

Animal[] animals = new Animal[3];
animals[0] = new Cat();
animals[1] = new Dog();
animals[2] = new Cow();

Now, let’s say you have an array of Animal objects, which can contain instances of a Cat, Dog, or Cow:

Animal[] animals = new Animal[3];
animals[0] = new Cat();
animals[1] = new Dog();
animals[2] = new Cow();

You can loop through the array of Animal objects and call the makeSound() method on each of them:

for (Animal animal : animals) {
animal.makeSound();
}

When you run this code, you’ll get the following output:

Meow
Woof
Moojj

This example demonstrates polymorphism because even though the array of Animal objects contains different types of animals, they can all be treated as objects of the Animal class and called using the same method makeSound(). The correct implementation of makeSound() for each type of animal will be called at runtime based on the actual type of the object.

In this example, the method makeSound() is polymorphic because it can take on multiple forms, depending on the actual type of the object. The same method name is used for all animal objects, but each object produces a different output. This is the essence of polymorphism and how it can be used to create flexible, reusable code.

How can we achieve run time polymorphism in Java?

Run time polymorphism in Java is also popularly known as Dynamic Binding or Dynamic Method Dispatch and can only be achieved through Method overriding.

Method overriding in Java is a feature that allows a subclass to provide a new implementation for a method that is already defined in its superclass. This allows the subclass to inherit the behavior of its superclass, but modify it to suit its specific needs. The overriding method must have the same method signature (i.e., the same method name, return type, and parameter list) as the method it is overriding in the superclass. Run time polymorphism also requires multiple inheritance to be present.

Method overriding allows the program to perform a single action in multiple ways, depending on the type of object it is applied to. This makes it possible to write code that is flexible and can handle multiple cases, making it easier to build complex applications that can adapt to changing requirements.

Here’s an example of how run-time polymorphism can be achieved in Java using method overriding:

class Shape {
public void draw() {
System.out.println("Drawing a Shape");
}
}

class Rectangle extends Shape {
@Override
public void draw() {
System.out.println("Drawing a Rectangle");
}
}

class Circle extends Shape {
@Override
public void draw() {
System.out.println("Drawing a Circle");
}
}

public class Main {
public static void main(String[] args) {
Shape shape1 = new Rectangle();
Shape shape2 = new Circle();
shape1.draw(); // Output: Drawing a Rectangle
shape2.draw(); // Output: Drawing a Circle
}
}

In this example, we have a superclass Shape with a method draw() that outputs “Drawing a Shape”. We then have two subclasses Rectangle and Circle that extends the Shape class and provide their implementation of the draw() method. The Rectangle class outputs “Drawing a Rectangle” while the Circle class outputs “Drawing a Circle”.

In the main method, we created two objects of the Shape class, shape1, and shape2, and assigned them to objects of the Rectangle and Circle classes, respectively. When we call the draw() method on shape1 and shape2, the correct implementation for each object is called dynamically at runtime, based on the type of the object. This is an example of run-time polymorphism, as the same method called (shape1.draw() and shape2.draw()) results in different behavior based on the type of the object.

Rules to be Followed When Executing Run Time Polymorphism

There are some rules to be followed when executing runtime polymorphism in Java:

  1. The overridden method must have the same method signature, which includes the method name, the number and type of parameters, and the return type.
  2. The overriding method cannot have a more restrictive access modifier than the overridden method. For example, if the original method is declared public, then the overriding method cannot be declared private.
  3. The overriding method can throw any unchecked exception, but it cannot throw any checked exceptions that were not defined by the original method.
  4. The overriding method must provide a method body.
  5. The instance of the object being called must match the type of reference to that object. For example, if you have a reference to an Animal object and the actual object is a Cat, you can only call Animal class methods. If you want to call methods that are specific to the Cat class, you need to cast the reference to a Cat type.
  6. You cannot override static methods, only instance methods.
  7. You cannot override final methods, as they cannot be changed.
  8. When calling an overridden method from within the subclass, you can use the super keyword to call the original implementation of the method in the superclass.

Concept of Interfaces in Java

Interfaces and abstract classes are two fundamental Java concepts for defining contracts between classes and implementing polymorphism.

An interface is a set of abstract methods that declare the methods that a class must implement but do not provide an implementation for them. In Java, interfaces are declared with the interface keyword, and any class that implements an interface must implement all of its methods. An interface is a useful concept for designing contracts between classes because it allows you to declare what methods a class must have without requiring you to specify how those methods should be implemented.

Here’s an example of how you can use interfaces to define a contract between classes in Java:

interface Drawable {
void draw();
}

class Shape implements Drawable {
@Override
public void draw() {
System.out.println("Drawing a Shape");
}
}

class Rectangle implements Drawable {
@Override
public void draw() {
System.out.println("Drawing a Rectangle");
}
}

class Circle implements Drawable {
@Override
public void draw() {
System.out.println("Drawing a Circle");
}
}

public class Main {
public static void main(String[] args) {
Drawable[] drawables = {new Shape(), new Rectangle(), new Circle()};
for (Drawable drawable : drawables) {
drawable.draw();
}
}
}

In this example, we define an interface Drawable with a single draw method (). We then create three classes, Shape, Rectangle, and Circle, that implement the Drawable interface and provide their own implementation of the draw() method. In the main method, we create an array of Drawable objects and loop over it, calling the draw() method on each object. The JVM selects which implementation of the draw() method to invoke at the runtime, based on the kind of object being called.

Concept of Abstract class in Java

An abstract class is a type of class that cannot be instantiated but can contain both abstract methods (which must be implemented by any class that extends the abstract class) and concrete methods (which have an implementation). Abstract classes provide a way to define a common set of behavior for a group of related classes, as any class that extends an abstract class must implement its abstract methods.

Here’s an example of how you can use abstract classes to implement polymorphism in Java:

abstract class Shape {
abstract void draw();
}

class Rectangle extends Shape {
@Override
void draw() {
System.out.println("Drawing a Rectangle");
}
}

class Circle extends Shape {
@Override
void draw() {
System.out.println("Drawing a Circle");
}
}

public class Main {
public static void main(String[] args) {
Shape[] shapes = {new Rectangle(), new Circle()};
for (Shape shape : shapes) {
shape.draw();
}
}
}

What is the difference between Compile Time Polymorphism and Run Time Polymorphism in Java?

Here is a comparison between Compile Time Polymorphism and Run Time Polymorphism in Java:

Conclusion

  • Polymorphism is a fundamental concept in object-oriented programming that refers to the ability of an object to take on multiple forms.
  • There are two main forms of polymorphism in Java: compile-time polymorphism and run-time polymorphism.
  • Compile-time polymorphism is also known as method overloading and occurs when multiple methods with the same name are defined, but with different parameters.
  • Run-time polymorphism is also known as method overriding and occurs when a subclass provides a different implementation of a method that is already defined in its superclass.
  • There are some rules to be followed when overriding methods, such as ensuring that the method signature is the same and that the access modifier is not more restrictive.
  • Interfaces and abstract classes can also be used to define contracts between classes and to achieve polymorphism.
  • By understanding and implementing polymorphism, developers can write code that is more flexible, maintainable, and scalable.
  • Overall, runtime polymorphism is an important concept to master for anyone looking to become proficient in Java programming.

--

--

AlishaS
Javarevisited

I am enthusiastic about programming, and marketing, and constantly seeking new experiences.