Polymorphism in Java:Unleashing Flexibility and Reusability

Introduction: Polymorphism in Java

The word Polymorphism is derived from two greek words: poly and morphs. “Poly” means many and “morphs” means forms. So Polymorphism means “many forms”.

Polymorphism in Java
Image by brgfx on Freepik

Polymorphism is one of the most important tenets of OOPS.We can perform an action in different ways. For example we can perform the add operation in many ways based on the input argument.

public int add(int x, int y){…}
public float add(float x, float y){…}

Two Ways to Shape Polymorphism in Java:-

  1. Compile Time Polymorphism: Method Overloading/Static Binding/Early Binding.
  2. Runtime Polymorphism:Method Overriding/Dynamic Binding/ Late Binding.

Compile Time Polymorphism in Java: Method Overloading/Static Binding/Early Binding

Compile time polymorphism, also known as static polymorphism or method overloading, is a form of polymorphism in Java where multiple methods with the same name but different parameters are defined within a class.

Here, the object type determination(binding) happens at compile time and hence it is also called as early binding.Private, static and final methods can be only overloaded as they can not be overridden.

Here, we define two or more methods with same name but with different parameters.The return type may or may not be same.

Compile Time Polymorphism in Java Example

public class StringPrinter {
public void print(String s1) {
System.out.println(s1);
}
public void print(String s1, String s2) {
System.out.println(s1 +” and “+ s2);
}
public static void main(String[] args) {
StringPrinter sp = new StringPrinter();
sp.print(“Apple”);
sp.print(“Apple”,”Orange”);
}
}
Result output:-
Apple
Apple and Orange

Rules for Overloading

1) Method signature consists of number of arguments, type of arguments and order of arguments if they are of different types.We need to change the method signature to overload a method.

public class Addition {
public int add(int x, int y) {
return  x + y;
}
public int add(int x, int y, int z) {  //Valid
return  x + y + z;
}
}

2) Return type of method is never part of method signature, so only changing the return type of method does not amount to method overloading.

public class Addition {
public int add(int x, int y) {
return  x + y;
}
public float add(int x, int y) {  // Not valid – Compilation error
return  x + y + z;
}
}

3) Overloaded method can throw the same exception, a different exception or it simply does not throw any exception; no effect at all on method loading.

public class Addition {
public int add(int x, int y) {
return  x + y;
}
public int add(int x, int y, int z) throws Exception {  //Valid
return  x + y + z;
}
}

Runtime Polymorphism in Java:Method Overriding/Dynamic Binding/ Late Binding

This happens when the java compiler is not able to resolve the binding  for the method call at the compile time as the method is defined in parent and also overridden in child classes.So at the runtime the actual object is used to identify the method call.

Runtime Polymorphism in Java Example

Let’s take an example:- Here the parent is Animal and Dog and Cat are it’s child classes.

Animal Class
public class Animal {
public String getAnimalType(){
return”Animal”;
}
}

Dog Class
public class Dog extends Animal{
public String getAnimalType(){
return”Dog”;
}
}

Cat Class
public class Cat extends Animal{
public String getAnimalType(){
return”Cat”;
}
}

AnimalCheck Class
public class AnimalCheck {
public static void main(String[] args) {
Animal animal1 = new Dog();
Animal animal2 = new Cat();
System.out.println(animal1.getAnimalType());
System.out.println(animal2.getAnimalType());
}
}

In the above example the binding for animal1.getAnimalType() and animal2.getAnimalType() is resolved at the run time based on the created object i.e., Dog or Cat.

Result output:-
Dog
Cat

Rules for Overriding

1) The method argument list(number of arguments, type of arguments and order of arguments) in overridden and overriding methods must be exactly same.

2) The return type of overriding method can be child class of return type declared in overridden method.

public class Animal {
public Animal getAnimal(){
return this;
}
}

public class Dog extends Animal{
@Override
public Dog getAnimal(){
return this;
}
}

3) Above all rules, private, static and final methods can not be overridden in java in any way. As simple as that !!

4) Overriding method can not throw checked Exception higher in hierarchy than thrown by overridden method. Let’s say for example overridden method in parent class throws FileNotFoundException, the overriding method in child class can throw FileNotFoundException; but it is not allowed to throw IOException or Exception, because IOException or Exception are higher in hierarchy i.e. super classes of FileNotFoundException.

public class Animal {
public Animal getAnimal() throws FileNotFoundException{
return this;
}
}

public class Dog extends Animal{
@Override
public Dog getAnimal() throws IOException{ // Compilation error
return this;
}
}

Also overriding method can throw any unchecked (runtime) exception, regardless of whether the overridden method declares the exception.

public class Dog extends Animal{
@Override
public Dog getAnimal() throws NullPointerException{// Valid
return this;
}
}

5) Also note that overriding method can not reduce the access scope of overridden method. Put in simple words, if overridden method in parent class is protected, then overriding method in child class can not be private. It must be either protected (same access) or public (wider access).

public class Animal {
protected Animal getAnimal(){
return this;
}
}

public class Dog extends Animal{
@Override
private Dog getAnimal(){// Invalid – Compilation Error
return this;
}
}

public class Dog extends Animal{
@Override
public Dog getAnimal(){//Valid – Wider access
return this;
}
}

Key Advantages of Polymorphism in Java

Polymorphism in Java offers several significant benefits that contribute to the flexibility, extensibility, and maintainability of code. Here are some key advantages:

  1. Code Reusability: Polymorphism allows for the creation of code that can be reused across multiple classes and objects. By designing classes with common interfaces or superclasses, methods can be written to accept objects of those types, enabling the same code to be used with different implementations.
  2. Flexibility and Extensibility: Polymorphism enables the addition of new classes or behaviors without modifying existing code. New classes can be created that inherit from existing superclasses or implement common interfaces, seamlessly integrating into the existing codebase.
  3. Modularity and Organized Design: Polymorphism promotes modular design principles by separating the behavior and implementation details of classes. It allows developers to focus on specific functionalities in individual classes while maintaining a unified interface for interaction with other classes.
  4. Simplified Code Maintenance: Polymorphism reduces code duplication by providing a unified interface for common behaviors. When changes are required, modifications can be made in a centralized manner, minimizing the need to modify code in multiple places.
  5. Runtime Flexibility: Polymorphism enables dynamic method dispatch, allowing the appropriate method implementation to be determined at runtime based on the actual object type. This dynamic binding supports polymorphic behavior and facilitates the implementation of complex and customizable runtime scenarios.
  6. Code Readability and Understandability: Polymorphism enhances code readability by promoting a more intuitive and natural representation of real-world relationships. It allows developers to write code that reflects the underlying concepts and relationships between different entities in a more straightforward and expressive manner.
  7. Enhanced Testability: Polymorphism facilitates the creation of testable code by allowing the use of mock objects or test doubles that implement the same interface or extend the same superclass. This enables easier isolation and testing of individual components and promotes effective unit testing practices.
  8. Polymorphic Collections: Polymorphism is often leveraged in the Java Collections Framework, enabling the creation of collections that can hold objects of different subclasses but share a common superclass or interface. This simplifies the handling and manipulation of collections with heterogeneous objects.

Overall, polymorphism in Java empowers developers to write flexible, modular, and reusable code. It supports the principles of abstraction and encapsulation, allowing for effective code organization and maintenance. By leveraging polymorphism, developers can build robust and scalable applications that are easier to understand, extend, and maintain.

Real Life Examples for Polymorphism in Java

Here are a few real-life examples that showcase the use of polymorphism in Java:

  1. Animal Hierarchy: Consider a scenario where you have different types of animals such as dogs, cats, and birds. You can create an Animal superclass with common attributes and behaviors. Each specific animal type can inherit from this superclass and override methods like makeSound() or move(). This allows you to treat all animals uniformly and perform common operations on them, regardless of their specific type.
  2. Media Player: In a media player application, you may have different types of media files like audio files, video files, and image files. You can create a common interface called MediaPlayer and define methods like play(), pause(), and stop(). Each media file type can implement this interface and provide its own implementation of these methods. By programming against the MediaPlayer interface, you can handle different media files uniformly, allowing users to interact with them in a consistent manner.
  3. Shape Rendering: In a graphics application, you may have different types of shapes to render, such as circles, rectangles, and polygons. You can create a Shape interface or superclass with a method like draw(). Each specific shape class can implement this method and provide its own logic to render the shape. By treating all shapes as instances of the Shape interface or superclass, you can render them uniformly without knowing their specific implementation details.
  4. Vehicle Transportation: In a transportation system, you may have different types of vehicles like cars, bicycles, and motorcycles. You can define a common interface or superclass called Vehicle with methods like start(), accelerate(), and stop(). Each vehicle type can implement these methods based on their unique characteristics. By programming against the Vehicle interface or superclass, you can control and manage different vehicles in a consistent manner.
  5. Banking Transactions: In a banking application, you may have different types of transactions like deposits, withdrawals, and transfers. You can create a common interface called Transaction and define methods like execute(). Each transaction type can implement this interface and provide its own logic to perform the specific transaction. By treating all transactions as instances of the Transaction interface, you can handle them uniformly, simplifying transaction processing and management.

These real-life examples demonstrate how polymorphism enables the treatment of different objects uniformly through a common interface or superclass. By leveraging polymorphism, you can write code that is flexible, reusable, and extensible, accommodating various types and behaviors in a consistent manner.

FAQs about Polymorphism in Java

Q: What is polymorphism in Java?

A: Polymorphism in Java refers to the ability of objects of different classes to be treated as objects of a common superclass or interface. It allows for code flexibility and reusability through a unified interface.

Q: What is the difference between compile-time polymorphism and runtime polymorphism in Java?

A: Polymorphism in Java includes both compile-time polymorphism, achieved through method overloading, and runtime polymorphism, achieved through method overriding. Compile-time polymorphism is determined during compilation, while runtime polymorphism is resolved at runtime based on the actual object type.

Q: How is polymorphism achieved in Java?

A: Polymorphism in Java is achieved through inheritance, method overriding, and method overloading. Inheritance allows objects of a subclass to be treated as objects of their superclass, while method overriding enables a subclass to provide its own implementation of a method inherited from the superclass.

Q: What is method overloading in Java polymorphism?

A: Method overloading, a form of polymorphism in Java, allows multiple methods with the same name but different parameter lists to exist within a class. The appropriate method is chosen during compilation based on the arguments passed.

Q: What is method overriding in Polymorphism in Java?

A: Method overriding, a key aspect of polymorphism in Java, occurs when a subclass provides its own implementation of a method already defined in its superclass. The subclass method must have the same name, return type, and parameter list as the superclass method.

Q: Can a subclass method override multiple methods from its superclass in Polymorphism in Java?

A: Yes, a subclass method in Polymorphism in Java can override multiple methods from its superclass as long as they have different method signatures. Each method in the subclass must adhere to the overriding rules, matching the method signature of the superclass method it intends to override.

Q: Can static methods be overridden in Polymorphism in Java?

A: No, static methods cannot be overridden in Polymorphism in Java. They are resolved based on the reference type at compile-time rather than the actual object type at runtime. Subclasses can declare methods with the same signature, but it is considered method hiding rather than overriding.

Q: What is the significance of the “super” keyword in method overriding in Polymorphism in Java?

A: In Polymorphism in Java, the “super” keyword is used to refer to the superclass version of a method. It is employed to invoke the superclass’s method implementation when a method is overridden in the subclass. This allows for the reuse of superclass behavior in the subclass.

Q: Can constructors be overridden in Polymorphism in Java?

A: No, constructors cannot be overridden in Polymorphism in Java. Each class must define its own constructors, and they are not inherited like methods. However, a subclass constructor can invoke the constructor of its superclass using the “super” keyword.

Q: How does polymorphism contribute to code reusability and extensibility in Java?

A: Polymorphism in Java enhances code reusability and extensibility by allowing objects of different classes to be treated uniformly through a common interface or superclass. This simplifies code maintenance, promotes modular design, and enables the addition of new classes without modifying existing code that relies on the common interface or superclass.

Q: What is dynamic method dispatch in Polymorphism in Java?

A: Dynamic method dispatch in Polymorphism in Java refers to the mechanism where the specific implementation of a method is determined at runtime based on the actual object type rather than the reference type. It allows for the invocation of overridden methods and enables polymorphic behavior in Java.

Q: Can we override a method and change its return type in Polymorphism in Java?

A: No, it is not possible to change the return type of a method during method overriding in Polymorphism in Java. The return type of the overriding method must be the same or a subtype of the return type in the superclass.

Q: Can we have polymorphism without inheritance in Java?

A: Polymorphism in Java is closely associated with inheritance, but it is possible to achieve polymorphic behavior without direct inheritance by utilizing interfaces. Interfaces allow unrelated classes to implement common methods, enabling them to be treated polymorphically.

Q: Can we overload methods based on their return types in Polymorphism in Java?

A: No, Polymorphism in Java does not support method overloading based solely on different return types. Method overloading is determined by the number, types, and order of the method’s parameters.

Q: Is polymorphism applicable only to instance methods in Polymorphism in Java?

A: No, polymorphism in Polymorphism in Java is applicable to both instance methods and static methods. However, the selection of the appropriate method is determined by the type of the object at runtime for instance methods and by the reference type at compile-time for static methods.

These FAQs cover a wide range of questions related to polymorphism in Java, providing valuable insights into its concepts, behavior, and usage.

Conclusion:

Throughout this article, we explored the concept of polymorphism in Java, which involves inheritance, method overriding, and dynamic method dispatch.

Understanding and effectively utilizing polymorphism in Java is essential for writing robust, flexible, and scalable software solutions. It promotes code reuse, enhances code readability, and simplifies complex programming scenarios.

3 comments

  1. Besides providing a simple and straight definition and explanation, self explanatory examples made the concept crisp clear.

    Some of eye-catching and uncommon examples include:
    public int add(int x, int y){…}
    public float add(float x, float y){…}
    This example clarifies the confusion of different return type w.r.t method overloading, it clearly conveys that return type has to do nothing with method overloading. If the argument types are different then different return type methods can also become a valid case of overloading.

    Overloaded method can throw the same exception, a different exception or it simply does not throw any exception; no
    effect at all on method loading.
    This conveys that throwing exception has to do nothing with overloading, it only matters in overriding.

    Also overriding method can throw any unchecked (runtime) exception, regardless of whether the overridden method
    declares the exception.
    It is very common to know that overriding method can’t throw checked Exception higher in hierarchy than that of its overridden method but the relation of overriding with unchecked exceptions is rarely known.

    Thanks for making us understand all possible scenarios.

Leave a Reply

Your email address will not be published. Required fields are marked *