Types of Java Polymorphism
There are two main types of polymorphism in Java:
1. Compile-time polymorphism (also known as static polymorphism)
2. Runtime polymorphism (also known as dynamic polymorphism)
Compile-time polymorphism is achieved through method overloading, while runtime polymorphism is achieved through method overriding.
Method overloading occurs when a class has multiple methods with the same name but different parameters. The compiler determines which method to call based on the number, types, and order of arguments passed.
On the other hand, method overriding occurs when a subclass defines a method with the same signature as a method in its superclass. The JVM determines which method to call based on the object's actual class at runtime.
Java also supports operator overloading to a limited extent, but only for the + operator, which can be used for string concatenation & addition.
Compile-Time Polymorphism in Java
Compile-time polymorphism, also known as static polymorphism, is a type of polymorphism that is resolved at compile time. The most common forms of compile-time polymorphism in Java are method overloading and limited operator overloading.
Method overloading allows a class to have multiple methods with the same name but different parameters. The compiler determines which method to call based on the number, types, & order of arguments passed.
Let’s see a simple example of method overloading:
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
public int add(int a, int b, int c) {
return a + b + c;
}
}
In this example, the Calculator class has three methods named add, but with different parameters. The first method takes two int parameters, the second takes two double parameters, and the third takes three int parameters.
When you call the add method, the compiler will determine which version of the method to call based on the arguments passed:
Calculator calc = new Calculator();
int result1 = calc.add(5, 10);
double result2 = calc.add(3.14, 2.71);
int result3 = calc.add(1, 2, 3);
The compiler chooses the appropriate add method based on the number and types of arguments, ensuring that the correct method is called at compile time.
Method overloading allows you to create cleaner and more readable code by providing methods with the same name but different parameters, making it easier to understand and maintain.
Operator Overloading: Java provides limited support for operator overloading. The only operator that can be overloaded in Java is the + operator, which is used for string concatenation and addition of primitive types.
For Example:
String str1 = "Hello";
String str2 = "World";
String result = str1 + " " + str2; // Concatenation using + operator
System.out.println(result); // Output: Hello World
int a = 10;
int b = 20;
int sum = a + b; // Addition using + operator
System.out.println(sum);
Output:
30
In this example, the + operator is used for string concatenation when applied to String objects and for addition when applied to integer values.
Templates (Generics): Java does not have templates like C++. However, it uses generics, which provide a way to write parameterized types and methods. Generics allow you to create classes, interfaces, and methods that can work with different data types while providing type safety at compile time.
For Example:
public class GenericClass<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
In this example, GenericClass is a parameterized class that can work with any data type T. The setValue method accepts a value of type T, and the getValue method returns a value of type T.
You can create instances of GenericClass with different data types:
GenericClass<String> stringClass = new GenericClass<>();
stringClass.setValue("Hello");
String stringValue = stringClass.getValue();
GenericClass<Integer> integerClass = new GenericClass<>();
integerClass.setValue(10);
int integerValue = integerClass.getValue();
Generics provide type safety and eliminate the need for explicit type casting, making the code more reusable and less prone to runtime errors.
Runtime Polymorphism in Java
Runtime polymorphism, also known as dynamic polymorphism, is a type of polymorphism that is resolved at runtime. In Java, runtime polymorphism is achieved through method overriding.
Method overriding occurs when a subclass defines a method with the same signature (name, parameters, & return type) as a method in its superclass. When an overridden method is called on an object, the JVM determines which implementation of the method to execute based on the actual class of the object at runtime.
For example :
public class Animal {
public void makeSound() {
System.out.println("The animal makes a sound");
}
}
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("The cat meows");
}
}
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("The dog barks");
}
}
In this example, we have a superclass called Animal and two subclasses, Cat and Dog. The Animal class has a method called makeSound, which is overridden by both subclasses.
Now, let's see how runtime polymorphism works:
Animal animal1 = new Cat();
Animal animal2 = new Dog();
animal1.makeSound(); // Output: The cat meows
animal2.makeSound(); // Output: The dog barks
In this code, we create two Animal variables, animal1, and animal2, but we assign them objects of the Cat and Dog subclasses, respectively. When we call the makeSound method on each variable, the JVM determines which implementation of the method to call based on the object's actual class at runtime.
This gives you the freedom to write more flexible and maintainable code because we can treat objects of different subclasses as if they were of the superclass type while still ensuring that the correct implementation of the method is called.
Advantages of Polymorphism in Java
1. Code reusability: Polymorphism allows you to write code that can work with objects of multiple related types. This means you can write a single method that accepts a superclass type as a parameter, & that method can be used with any subclass objects. This promotes code reusability & reduces duplication.
2. Flexibility: Polymorphism makes your code more flexible by allowing you to write methods that can adapt to different types of objects. You can add new subclasses without modifying existing code that uses the superclass, as long as the new subclasses adhere to the same interface or superclass contract.
3. Maintainability: Polymorphism can make your code more maintainable by reducing the need for conditional statements and type checking. Instead of writing separate code paths for each subclass, you can treat objects uniformly based on their shared superclass or interface. This makes the code cleaner, easier to understand, and less prone to errors.
4. Extensibility: Polymorphism enables you to extend the behavior of existing classes through inheritance & method overriding. You can create specialized subclasses that inherit from a common superclass & override methods to provide specific implementations. This allows you to build upon existing code & add new features without modifying the original classes.
5. Loose coupling: Polymorphism promotes loose coupling between objects. When you write code that relies on a superclass or interface type rather than specific subclasses, your code becomes less dependent on the concrete implementations. This makes it easier to change or replace the subclasses without affecting the rest of the codebase.
Disadvantages of Polymorphism in Java
1. Complexity: Polymorphism can make your code more complex, especially if you have a deep hierarchy of classes or a large number of overloaded methods. It can be challenging to keep track of which method is being called in different situations, leading to potential confusion & bugs if not managed properly.
2. Performance overhead: There may be slight performance-related issues with polymorphism, especially with runtime polymorphism (method overriding). When a method is overridden, the JVM needs to determine the actual class of the object at runtime & then call the appropriate method implementation. This dynamic dispatch process adds a small amount of overhead compared to direct method calls.
3. Naming conflicts: When using method overloading, it's possible to inadvertently create naming conflicts if you're not careful with the method signatures. If you have multiple methods with the same name & similar parameter types, it can be confusing to determine which method will be called in a given situation. It's important to choose method names & parameters carefully to avoid ambiguity.
4. Inappropriate use: Polymorphism can be misused or overused, leading to code that is harder to understand & maintain. If you create overly complex hierarchies or use polymorphism in situations where it's not necessary, it can make your code more difficult to reason about & debug. It's important to use polymorphism judiciously & in appropriate situations.
5. Learning curve: Understanding and effectively applying polymorphism requires a solid grasp of object-oriented programming concepts. Developers who are new to OOP may find polymorphism challenging to comprehend and implement correctly. It's important to invest time in learning and practicing polymorphism to use it effectively in your Java programs.
Frequently Asked Questions
Can you override static methods in Java?
No, you cannot override static methods in Java. Static methods are associated with the class itself, not instances of the class, so they cannot be overridden by subclasses.
What happens if you call a method on a null object reference?
If you call a method on a null object reference, a NullPointerException will be thrown at runtime. It's important to check for null before invoking methods on object references.
Can you use polymorphism with interfaces in Java?
Yes, polymorphism can be achieved through interfaces in Java. You can define methods in an interface and have multiple classes implement that interface, allowing you to treat objects of those classes polymorphically based on the interface type.
Conclusion
In this article, we discussed polymorphism in Java, its definition, types (compile-time and runtime polymorphism), method overloading and overriding, and its advantages and disadvantages. Polymorphism is a fundamental concept in object-oriented programming that allows objects to take on multiple forms and behave differently based on their specific types.
You can also check out our other blogs on Code360.