Polymorphism in Java Explained!
6 min read
Polymorphism is a fundamental concept in Java that allows Java developers to write flexible and reusable code, enhancing the maintainability of applications. Polymorphism, which literally means "many forms," enables a single method to exhibit different behaviors based on the object it acts upon. In Java, this principle works hand-in-hand with other OOP concepts like inheritance, encapsulation, and abstraction.
Understanding polymorphism is vital for any Java programmer, not only to write efficient code but also to adhere to OOP best practices. This article explores the concept of polymorphism in Java, including the different types, use cases, and differences with inheritance. Read on!
Types of Polymorphism in Java
Java implements polymorphism in two main ways: compile-time and runtime polymorphism.
Compile Time Polymorphism
Compile Time Polymorphism, also known as static polymorphism, occurs when multiple methods in the same class share the same name but differ in their parameter lists (either in the number of parameters, types of parameters, or both). The decision on which method to call is made by the compiler at compile time, based on the method signature.
Example of Compile Time Polymorphism:
Consider a class Multiplier
that has methods to multiply numbers, but the methods vary based on the types and the number of parameters.
class Multiplier {
// Method to multiply two integers
static int Multiply(int a, int b) {
return a * b;
}
// Method to multiply two double values
static double Multiply(double a, double b) {
return a * b;
}
}
class Main {
public static void main(String[] args) {
System.out.println("Integer Multiplication: " + Multiplier.Multiply(2, 4)); // Output: 8
System.out.println("Double Multiplication: " + Multiplier.Multiply(3.5, 5.1)); // Output: 17.85
}
}
In this example, the Multiply
method is overloaded with two different parameter sets, and the compiler decides which method to call based on the types of arguments passed.
Runtime Polymorphism (Method Overriding)
Runtime Polymorphism, also known as dynamic polymorphism, occurs through method overriding. This happens when a subclass provides a specific implementation for a method that already exists in its superclass. Unlike compile-time polymorphism, the decision on which method to execute is made at runtime, depending on the type of the object.
Example of Runtime Polymorphism
Consider a superclass Shape
and its subclasses Circle
and Square
, each providing its own implementation of the draw
method.
class Shape {
void draw() {
System.out.println("Drawing a shape");
}
}
class Circle extends Shape {
@Override
void draw() {
System.out.println("Drawing a circle");
}
}
class Square extends Shape {
@Override
void draw() {
System.out.println("Drawing a square");
}
}
class Main {
public static void main(String[] args) {
Shape shape1 = new Circle();
Shape shape2 = new Square();
shape1.draw(); // Output: "Drawing a circle"
shape2.draw(); // Output: "Drawing a square"
}
}
In this example, the method draw
is overridden in both Circle
and Square
classes. At runtime, the JVM determines the actual object type and invokes the corresponding draw
method. Thus, even though the reference type is Shape
, the method that gets executed depends on the actual object type (Circle
or Square
).
Differences Between Inheritance and Polymorphism in Java
Inheritance and polymorphism are two fundamental concepts in Object-Oriented Programming (OOP), especially in Java. They are often used together but serve different purposes in software development. Understanding their differences is crucial for effective OOP design.
Inheritance is a mechanism in Java where one class (the child or subclass) inherits the attributes and methods of another class (the parent or superclass). It's a way to form new classes using classes that have already been defined. The primary motive behind inheritance is to reuse existing code and to establish a relationship between different classes.
Characteristics of Inheritance in Java
Enables code reuse by inheriting properties and behaviors from a parent class to a child class.
Establishes a "is-a" relationship. For example, a
Dog
class inheriting from anAnimal
class implies that aDog
is anAnimal
.Supports method overriding, allowing subclasses to provide a specific implementation of a method already defined in its superclass.
Inheritance is a static feature, meaning the structure (which class inherits from which) is fixed at compile-time.
class Animal {
void eat() {
System.out.println("Animal eats");
}
}
class Dog extends Animal {
void bark() {
System.out.println("Dog barks");
}
}
class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat(); // Inherited method
dog.bark(); // Own method
}
}
Polymorphism in Java allows objects of different classes to be treated as objects of a common super class. The most common use of polymorphism is when a parent class reference is used to refer to a child class object. It literally means "many forms" and allows methods to do different things based on the object it is acting upon.
Characteristics of Polymorphism
Polymorphism lets a method perform different operations based on the object that it is acting upon, enabling a single interface to handle different underlying forms (data types).
It is mainly divided into two types: compile-time (method overloading) and runtime (method overriding) polymorphism.
Establishes a "can-do" relationship. For example, a
Shape
class having adraw
method, and its subclassesCircle
andSquare
implementing thedraw
method in their own way.Polymorphism is dynamic, allowing method calls to be resolved at runtime.
Example:
class Shape {
void draw() {
System.out.println("Drawing a shape");
}
}
class Circle extends Shape {
@Override
void draw() {
System.out.println("Drawing a circle");
}
}
class Square extends Shape {
@Override
void draw() {
System.out.println("Drawing a square");
}
}
class Main {
public static void main(String[] args) {
Shape shape;
shape = new Circle();
shape.draw(); // Drawing a circle
shape = new Square();
shape.draw(); // Drawing a square
}
}
Key Differences
Purpose: Inheritance is about creating new classes from existing ones (reuse and extension), while polymorphism is about using a class interface for different underlying forms (object types).
Relationship: Inheritance establishes an "is-a" relationship, whereas polymorphism provides a "can-do" relationship.
Flexibility: Polymorphism provides greater flexibility through dynamic binding, while inheritance can lead to a rigid class structure.
Design Principle: Inheritance is used for code reuse and establishing relationships, while polymorphism is used for dynamic method invocation, which is crucial for implementing loose coupling in applications.
Conclusion
In conclusion, polymorphism in Java offers a powerful tool for writing adaptable and maintainable code. Through method overloading and overriding, Java developers can leverage this feature to create applications that are robust and scalable. Inheritance on the other hand creates a direct relationship between classes, allowing for code reuse and establishing a hierarchical order. This "is-a" relationship is crucial for defining and extending classes, fostering code organization and maintainability.
The synergy of inheritance and polymorphism is fundamental to creating robust and scalable Java applications. Inheritance lays the foundation by establishing relationships and providing shared attributes and methods, while polymorphism builds upon this by allowing these methods to perform different functions depending on the object they are acting upon. This combination promotes code reusability, reduces redundancy, and enhances the capability to handle complexity with simplicity.
Understanding the nuanced differences between these two concepts is key for any Java programmer aiming to craft effective, efficient, and elegant software solutions. Properly leveraging inheritance and polymorphism not only leads to cleaner and more understandable code but also aligns with the best practices of object-oriented design, making your codebase more adaptable to change and easier to extend.