Cyber Monday Deal : Flat 30% OFF! + free self-paced courses - SCHEDULE CALL
Polymorphism in object oriented programming is essential because it improves code reusability, flexibility, and maintainability. It enables the treatment of objects of distinct classes as objects of a single superclass or interface. In this blog, we will look at the notion of polymorphism in OOPs (Object-Oriented Programming), its kinds - compile-time polymorphism (method overloading) and runtime polymorphism (method overriding), and show what is polymorphism in oops with examples to help you understand these concepts. Evolve as an expert software tester to gain in-depth knowledge and become a master in this field.
Compile-Time Polymorphism / Method Overloading
Compile-time polymorphism, also known as static polymorphism, is a mechanism that is accomplished through method overloading. This particular form of polymorphism allows for multiple methods within a class that share the same name but possess different parameters. When invoking a method with compile-time polymorphism, the compiler determines which specific method to execute based on factors such as the number, order, and types of arguments provided during the invocation process. By leveraging this feature, developers can write more flexible and expressive code by reusing method names while adapting them to handle varying input scenarios.
Method overloading is a feature in object-oriented programming that allows multiple methods with the same name but different parameters to coexist within a class. This enables developers to create more flexible and expressive code by providing different ways to invoke the same method depending on the specific requirements.One of the main advantages of compile-time polymorphism, achieved through method overloading, is its ability to improve code readability and maintainability. Developers can easily understand their purpose using descriptive names for overloaded methods without relying on comments or documentation. For example, imagine having a class called "MathUtils" that contains various overloaded methods for calculating areas:
```java public class MathUtils { public double calculateArea(double radius) { // Calculate area of circle } public double calculateArea(double length, double width) { // Calculate area of rectangle } public double calculateArea(double base, double height) { // Calculate area of triangle } } ```
By employing this approach, the determination of which method to utilize becomes evident by considering the quantity and types of arguments provided during invocation. For instance, if we desire to compute the area of a rectangle with sides measuring 5 units each, we would invoke the method `calculateArea(5, 5)`.For facilitating clarity in method selection based on argument specifications, compile-time polymorphism offers another advantage: type safety. This means that during compilation, potential type-related errors are identified and flagged by the compiler itself before execution occurs.
By catching these issues early on, developers can ensure that their code adheres to expected data types and minimize runtime errors caused by incompatible or mismatched variables.Since overloaded methods have different parameter signatures (i.e., different combinations of argument types), it helps prevent accidental misuse or incorrect invocations at compile time. The compiler ensures that only valid method calls are made based on the available choices.
Compile-time polymorphism also promotes code reusability by allowing developers to define similar behavior across multiple scenarios without duplicating code logic. For instance, if you have an application that needs to convert temperature values between Celsius and Fahrenheit scales in various parts throughout your codebase:
```java public class TemperatureConverter { public static double convertToFahrenheit(double celsius) { // Convert Celsius to Fahrenheit } public static double convertToCelsius(double fahrenheit) { // Convert Fahrenheit to Celsius } } ```
By employing method overloading for the `convert` method, you can effectively reuse the conversion logic without the need to create separate methods for each possible combination of temperature conversions.
However, it is crucial to understand that when overloading methods, only the compiler considers their signatures (i.e., names and parameters). This means that while multiple methods may share the same name, they must have different parameter lists to be distinguished from one another during compilation.The return type or exceptions a method throws do not affect its signature. Therefore, two methods with different return types cannot be overloaded solely based on their return type difference.
Example:
Let's consider an example where we have a class called "Calculator" that performs arithmetic operations such as addition:
```java public class Calculator { public int add(int num1, int num2) { return num1 + num2; } public double add(double num1, double num2) { return num1 + num2; } } ```
In this example, we have two overloaded methods named "add." The first method accepts two integers as parameters, while the second takes two doubles. During compilation time, when invoking `add`, if we pass integer values like `add(5, 10)`, the compiler resolves it to call the first `add` method with integer parameters. Similarly, passing decimal values like `add(3.14 , 6.28)` invokes the second `add` method with double parameters.
Method overloading allows us to create methods with the same name but different parameter lists, providing flexibility and code readability. It simplifies method naming conventions by using a single name for similar operations. However, it's important to note that only changing the return type of a method does not constitute overloading.
Pros:
Cons:
Run-time polymorphism is achieved through method overriding. To understand run-time polymorphism, consider an example involving an Animal (superclass) and a Dog (subclass). In this scenario, the Dog class overrides a method called makeSound() defined in its superclass, Animal.
When we create an instance of the Dog class and assign it to a variable of type Animal, such as:
Animal animal = new Dog();
At runtime, the decision on which overridden method to execute is based on the actual type of the object being referred to. In this case, even though we have assigned a Dog object to an Animal reference variable when we invoke the makeSound() method on that variable:
animal.makeSound();
The implementation of makeSound() in the Dog class will be executed instead of in the Animal class. This is because run-time polymorphism allows different methods to be executed based on the actual type of an object rather than just its reference type.
This concept is fundamental in object-oriented programming, enabling more dynamic and flexible behavior within our programs. By overriding methods in subclasses, we can provide specialized implementations while maintaining a common interface through inheritance.
``` Animal myDog = new Dog(); ```
Even though we are assigning an instance of Dog to a variable of type Animal, thanks to inheritance, this assignment is valid because every dog is also considered an animal. Now, if we call the makeSound() method using this reference variable:
``` myDog.makeSound(); ```
The output will be "Dog makes sound". This happens because, at runtime, Java determines that the actual type of myDog is Dog (even though it was declared as Animal), so it executes the overridden version of makeSound() defined in the Dog class.
This behavior showcases one important aspect of run-time polymorphism: even though we have used an Animal reference variable to hold our object, Java still uses dynamic binding during runtime to determine which specific version of makeSound() should be called based on its actual type.
Run-time polymorphism becomes particularly useful when dealing with collections or arrays containing objects from different subclasses but sharing some common superclasses. For example:
``` Animal[] animals = new Animal[2]; animals[0] = new Cat(); animals[1] = new Dog(); for (Animal animal : animals) { animal.makeSound(); } ```
Here, we have an array of Animal references that can hold objects from different subclasses. During the iteration, when calling makeSound() on each element, Java will dynamically determine which version of makeSound() to execute based on the actual type of each object.run-time polymorphism allows for flexible and dynamic method invocation in object-oriented programming. By overriding methods in subclasses, we can provide specialized implementations and let Java decide at runtime which implementation to execute based on the actual type of an object. Consider an example where we have a class hierarchy consisting of a base class, "Animal," and two subclasses - "Dog" and "Cat." This feature becomes particularly useful when dealing with collections or arrays containing objects from these subclasses but sharing the common superclass.
By utilizing run-time polymorphism, we can write more generic code without needing to check types or cast objects explicitly. This allows for greater flexibility in handling diverse objects within a collection or array, as the appropriate overridden methods will be invoked based on the actual type of each object during runtime.
Each subclass overrides the `makeSound` method from the parent class:
```java public class Animal { public void makeSound() { System.out.println("Generic animal sound"); } } public class Dog extends Animal { @Override public void makeSound() { System.out.println("Woof!"); } } public class Cat extends Animal { @Override public void makeSound() { System.out.println("Meow!"); } } ```
In this example, we create objects of both the `Dog` and `Cat` classes and invoke their respective `makeSound` methods. At runtime, Java determines which version of `makeSound` should be executed based on the object type.
Method overriding allows us to provide specialized implementations in subclasses while maintaining common behavior defined in superclasses. It facilitates code extensibility by allowing new functionalities specific to each subclass without modifying existing code.
Pros:
Cons:
This blog helps you understand and define polymorphism in oops. Polymorphism is an essential concept in OOPs that enables flexible coding practices by treating objects interchangeably within inheritance hierarchies or interfaces. Compile-time polymorphism (method overloading) and runtime polymorphism (method overriding) are two different types of polymorphism in oops that enhance code reusability, readability, and maintainability. By understanding these concepts and their practical examples, you can leverage the power of polymorphism to write efficient and scalable Java programs. Enhance your career with a QA Software Testing Training Course for beginners and professionals.
Testing with V Models: A Guide to Their Application
Understanding The Software Development Life Cycle (SDLC) in QA
Cyber Security
QA
Salesforce
Business Analyst
MS SQL Server
Data Science
DevOps
Hadoop
Python
Artificial Intelligence
Machine Learning
Tableau
Download Syllabus
Get Complete Course Syllabus
Enroll For Demo Class
It will take less than a minute
Tutorials
Interviews
You must be logged in to post a comment