Back to home
Logicmojo - Updated Dec 19, 2022



You may have observed a chameleon in the wild change colours depending on the situation. You can simply respond, "Because it is polymorphic," if someone asks "How it does that." In the world of programming, Java objects have a similar functionality in that each object can take different shapes. Java refers to this trait as polymorphism, where poly denotes numerous and morph refers to change (or "shape"). Take the Shape Class as an illustration.

It is entirely up to you which specific shape you choose. It can resemble a diamond, circle, polygon, rectangle, or any other shape. All of these are forms, yet they have different characteristics.

A similar example is this one. Drink water. It can exist in one of three states: gaseous, liquid, or solid. Three distinct forms of the same water are possible. Polymorphism is the term for this.

Inheritance, Abstraction, Polymorphism, and Encapsulation are the four fundamental principles of OOP (Object Oriented Programming). The following three of OOP's fundamental notions can be challenging for a beginner to understand at first (since Inheritance is a bit easy understand).

At practise, polymorphism lessens the developer's workload so that they can change the code in the precise places where the interactions differ in order to produce more specialised subclasses with some of these unique qualities and behaviours. You can disregard the rest of the code.

What is Polymorphism?

One of the fundamental ideas of OOP is polymorphism, which is defined as "various forms" in the literal sense. By adding two functions with the same name but different parameters, polymorphism investigates how to construct and use two methods with the same name to carry out two distinct functionality.

public class Main {

    public static void main(String[] args) {

        // using the first method

        Multiplier.Multiply(3,5);

        // using the second method

        Multiplier.Multiply(3.5,5.1);

    }

}

Let's look at how to define two functions with the name Multiply, for instance (). Using one, you can find the product of two integers, and using the other, you can find the product of two doubles.



Learn More

Types of Polymorphism

Java supports two types of polymorphism:

  1. Compile-time polymorphism (static polymorphism)

  2. Runtime polymorphism (dynamic polymorphism)

Compile-Time Polymorphism

This kind of polymorphism, sometimes known as static polymorphism, is accomplished by writing many methods with the same name in the same class, but with various amounts of parameters or parameters of various data types in each method. The three methods in the example below all have the same name, but each one accepts a different set of parameters or a different kind of data.
This type of polymorphism is achieved by function overloading or operator overloading.

Method Overloading:

When a class contains two or more methods with the same name, this is known as method overloading. However, the amount of parameters in the method call determines how a particular method is implemented.



package com.logicmojo;

class MultiplierDemo {

    static void Multiply(int a, int b)

    {

        System.out.println("Integer Multiplication, Result = "+ a*b);

    }

    // Method 2

    static void Multiply(double a, double b)

    {

        System.out.println("double Multiplication, Result = "+ a*b);

    }

    // Method 3

    static void Multiply(double a, double b, double c)

    {

        System.out.println("Three parameters, double Multiplication, Result = "+ a*b*c);

    }

}

public class Main {

    public static void main(String[] args) {

        // using the first method

        MultiplierDemo.Multiply(3,5);

        // using the second method

        MultiplierDemo.Multiply(3.5,5.1);

        // using the third method

        MultiplierDemo.Multiply(3.6,5.2, 6.3);

    }

}

The first approach requires two different integers as input, while the second and third methods both require two double parameters. Observe that when we call them, we don't provide any extra details. With the exception of the number or parameter type, the three calls are identical.

Function Overloading:

Multiple functions with the same name but different argument lists are allowed in C++. Based on the quantity and nature of the parameters supplied to the function, the compiler will choose which function to call.

Operator Overloading:

When used with user-defined data types, the operators +, -, *, etc., can have additional meanings thanks to a feature of C++.

Run time Polymorphism

This kind of polymorphism, also known as dynamic polymorphism, happens when a child class defines one of the parent class's member methods differently. It's known as method overriding. Runtime polymorphism is typically linked to upcasting. When a parent class refers to a member of the child class, this occurs.

Method Overriding

Method overriding is a procedure that allows the compiler to allow a child class to implement a specific method that already exists in the parent class.


Overriding is accomplished by using a superclass reference variable. The method to be called is determined by the object to which the reference variable refers. This is also referred to as Upcasting.

Upcasting occurs when the reference variable of the Parent class refers to the object of the Child class. As an example:

class  A{} 
class  B extends A{}  
A a=new B(); //upcasting

                        

Example :

Shape Class
package com.logicmojo;

// Super Class

class Shape{

    protected double length;

    Shape(double length){

        this.length = length;

    }

    void area(){

    }

}

// Child class

class Square extends Shape{

    //constructor

    Square(double side){

        super(side); // calling the super class constructor

    }

    //Overriding area() method

    void area(){

        System.out.println("Square Area = " + length*length);

    }

}

// Child class

class Circle extends Shape{

    //constructor

    Circle(double radius){

        super(radius); // calling the super class constructor

    }

    //Overriding area() method

    void area(){

        System.out.println("Circle Area = " + 3.14*length*length);;

    }

}

public class Main {

    public static void main(String[] args){

        Shape shape = new Square(5.0);

        // calling the area() method of the Square Class

        shape.area();

        shape =  new Circle(5.0); // upcasting

        // calling the area() method of the Circle Class

        shape.area();

    }

}


Example :

Multilevel Inheritance
//parent class
class Birds{
    // overriding move method
	void move(){
		System.out.println("Birds can fly.");
	}
}
// Sparrow class extends Birds class
class Sparrow extends Birds{
    
	void move(){
		System.out.println("Sparrow can walk as well as fly.");
	}
}

class Chick extends Sparrow{
    
	void move(){
		System.out.println("Chick can walk.");
	}
}
class Polymorphism{
    public static void main(String[] args) {
                
		Birds A = new Bird();
		A.move();
		A = new Sparrow();
		A.move();
		A = new Chick();
		A.move();
    }
}

Real-life example of Polymorphism

Think about a cell phone that allows you to save your contacts. Let's say someone has two phone numbers. Your phone has the ability to store two numbers under the same name so they are more easily accessible.
Similar to this, an object in Java only has one instance, but depending on the program's context, it may take different shapes. If you wanted to construct a function that would save two contact numbers for the same individual, you could do it by using the syntax void createContact (String name, int number1, int number2).

Now, not every person on your contact list needs to have two phone numbers. Many of them might just have one contact number. In such cases, you can build another method with the same name, i.e. createContact, rather than developing another method with a different name to save one number for a contact (). However, when using the void createContact function, only provide one contact number as a parameter rather than two (String name, int number1).

Additional Features of Polymorphism

In addition to method overloading and method overriding, polymorphism includes the following features.

  1. Coercion

  2. Internal Operator Overloading

  3. Polymorphic Variables or Parameters

  4. Subtype polymorphism

Coercion

Consider two variables, one with an integer data type and the other with a double data type, to help us comprehend this. We will get a type issue if we add these two numbers.
A smaller data type is automatically typecast into a more significant data type depending on the situation thanks to a built-in Java feature called coercion. The integer value in this instance will be typecast to a double value before addition occurs. Type mistake is thus prevented.

Coercion is the implicit conversion of one data type into another without altering its context. To avoid type mistakes, this conversion type is used.
In other words, coercion happens when a datum is present in one data type but its context calls for a different data type.
Concatenating an integer and a string is a common example.

String str="string"=2;

Operator Overloading

A polymorphic trait of a symbol or operator having several meanings (forms) depending on the context is referred to as operator or method overloading. For instance, string concatenation and mathematical addition both utilise the plus sign (+). In either scenario, the symbol's interpretation is solely dependent on context (specifically, the types of arguments).

String str = "2" + 2;
int sum = 2 + 2;
System.out.println(" str = %s sum = %d", str, sum);

Polymorphism in Java occurs when there are one or more classes or objects related to each other by inheritance.Because every object or instance variable in Java has an IS-A relationship with its own classes and subclasses, it may be said that every object or instance variable is a polymorphic variable.
Polymorphic variables are those that take on diverse values depending on the context of their execution.

According to concepts of polymorphism in Java Language, a method name can be associated with several arguments and return types whereas a field name can be connected with a variety of types.

Polymorphism subtypes

Subtype describes a type's ability to act as another type's subtype. When a subtype instance is present in a supertype context, the subtype's version of the operation runs when it is applied to the subtype instance. Take a look at a piece of code that creates random shapes, for instance. By creating a Shape class with a draw() method, Circle, Rectangle, and other subclasses that override draw(), a Shape array whose members contain references to Shape subclass instances, and by invoking the draw() method on each instance, you may express this drawing code more succinctly. The draw() method of the Circle, Rectangle, or any Shape instance is called when you call it.

class Europe{
	void data(){
		System.out.println("Europe is awesome");
	}
}

class Prague extends Europe{
	void data(){
		System.out.println("Yep it's prague");
	}
}
class Polymorphism{
    public static void main(String[] args) {
		Europe obj;
		obj = new Europe();
		obj.data();
		obj = new Prague();
		obj.data();
    }
}

Polymorphism vs. Inheritance

You can establish an Automobile class, then have a BMW class and a Mercedes class that both inherit from the Car class if you have two different car types, like BMW and Mercedes. This implies that they take on its characteristics and abilities.
You can suppose that the Car (parent class) can be either a BMW or a Mercedes thanks to polymorphism (child class). As a result, each of the two car types may be referred to by the parent class Car.

public class Car{}

Public class tata extends Car {}

Public class swift extends Car{}

Car myCar = new tata();

//…some code…

myCar = new swift();

//…some code…

This is a straightforward example of Polymorphism in Java language is upcasting in which an object from the child class is referenced by the parent class. You can gain access to the child class of the parent class by utilising upcasting. This means that you can access the methods that were only defined in the parent class, but you also have access to the method with the same name that was defined in the child class. When your application needs to decide which version of code to invoke during runtime, upcasting can be helpful.

Advantages of Polymorphism

  1. The fundamental benefit of polymorphism is code reuse; once a class is defined, it may be used repeatedly to produce an object.

  2. Compile-time polymorphism makes code more readable by allowing essentially identical functions to share the same name, which makes it simpler to comprehend the functions.

  3. Runtime polymorphism allows for the creation of the same method in both the parent and child classes.

  4. The code is simple to debug. While running code, you might save intermediate results in random areas of memory, which could be abused by other programme elements. Polymorphism gives computation the required structure and regularity so that it is simpler to debug.

Problems of Polymorphism in Java Language

  1. The difficulty of understanding the hierarchy of classes and their overriding methods makes it tough to implement code.

  2. Downcasting issues exist because it is impossible to downcast implicitly. Downcasting is the practise of casting to a child type or a common type to a unique type.

  3. Subclasses of a superclass occasionally make use of the superclass in unanticipated ways when the parent class design is not established appropriately. This results in corrupt code.

  4. Runtime polymorphism has the potential to negatively impact real-time performance (during the process). This is because decisions must be made at runtime regarding which variable or method to utilise.

  5. Use Cases of Polymorphism

    One use for polymorphism is the automatic invocation of the appropriate method based on the class utilising a single method name. For instance, the example that follows implements a Set of type Shape from the prior example and stores objects from several child classes like Circles and Squares is an example of the use of a single storage element to hold many types:

    Set<Shape> mapp = new HashSet<Shape>();
    
    mapp.add(circle1);
    
    mapp.add(circle2);
    
    mapp.add(square1);
    
    mapp.add(square2);
    
    for(Shape hs_element : mapp){
    
          hs_element.area();
    
    }
    

    In your code, polymorphism can also be used to replace conditional statements. As an illustration, the code below use a switch statement in the area method to select the appropriate code to execute depending on the first parameter supplied to it. You'll see that it produces the same outcome as the code example for runtime polymorphism above. However, this strategy is not that simple.

    package org.logicmojo;
    
    // Super Class
    
    class Shape{
    
        enum Type {
    
            SQUARE,
    
            CIRCLE
    
        }
    
        protected double length;
    
        Type typeof_shape;
    
        Shape(Type shape, double length){
    
            this.length = length;
    
            this.typeof_shape = shape;
    
        }
    
        double area(){
    
            double area = 0;
    
            switch (typeof_shape)
    
            {
    
                case SQUARE:
    
                    area =  length * length;
    
                    break;
    
                case CIRCLE:
    
                    area =  3.14*length * length;
    
                    break;
    
            }
    
            return area;
    
        }
    
    }
    
    public class Main {
    
        public static void main(String[] args) {
    
            Shape Circle = new Shape(Shape.Type.CIRCLE, 10);
    
            System.out.println("Circle Area equals = "+ Circle.area());
    
            Shape Square = new Shape(Shape.Type.SQUARE, 10);
    
            System.out.println("Square Area equals = "+ Square.area());
    
        }
    
    }
    
    

    Summary

    1. One of the four components of object-oriented programming is polymorphism.

    2. Polymorphism can be effectively used to create flexible, extensible class designs.

    3. Runtime polymorphism and compile-time polymorphism are the two main types of polymorphism.

    4. Method overriding is used to accomplish runtime polymorphism, whereas method overloading is used to achieve compile-time polymorphism.

    5. Although it doesn't support it, Java does internal operator overloading.

    6. Java also provides coercive polymorphism and parametric polymorphism in addition to runtime and compile-time.

    7. We can reuse code without recompiling it by using the inheritance and method overriding concepts.

    8. The method overloading technique enables access to related functions via the common name.

    9. Coercion polymorphism is used to achieve internal operator overloading.

    FAQs

    Explain the different types of Polymorphism used in java language?

    There are four varieties of polymorphism:
    – Runtime or Subtype polymorphism
    – Overloading or Parametric polymorphism
    – Compile-time or Ad hoc polymorphism
    – Casting or Coercion polymorphism

    What do you mean by Polymorphism?

    Polymorphism, one of the fundamental ideas in object-oriented programming, defines circumstances in which a specific thing appears in various forms. Polymorphism is a term used in computer science to describe a concept that enables us to access several object kinds through a single interface.

    What do you mean by overriding in OOPs?

    The ability to override a method that is already offered by one of a subclass's superclasses or parent classes in object-oriented programming allows a subclass or child class to provide a particular implementation of the function.

    Explain the difference between overriding and overloading?

    Overloading is the practise of having multiple methods with the same name but different parameters in the same class. When overriding, the same superclass and the child class both have the method signature (name and parameters).

    Conclusions

    This concludes our discussion of polymorphism in Java. I sincerely hope that you learned something from it and that it improved your knowledge. You can visit logicmojo to learn more about Java.

    Good luck and happy learning!








    Frequently Asked Questions (FAQs)


    In Java, polymorphism is an important notion in object-oriented programming (OOP) that allows objects of distinct classes to be treated as objects of a single superclass or interface. It allows separate classes to have alternative implementations of methods with the same name, which increases flexibility, extensibility, and code reusability. Polymorphism allows the same method to be called on many objects, resulting in varied actions based on the object's actual type at runtime.

    Polymorphism in Java is classified into two types: compile-time polymorphism (method overloading) and runtime polymorphism (method overriding).

    1. Compile-Time Polymorphism (Method Overloading): Method overloading is a type of compile-time polymorphism in which numerous methods with the same name but distinct parameter lists exist. The compiler decides the method to call based on the number, type, and order of parameters given during method invocation. Overloading methods allows you to specify numerous versions of a method that execute similar actions but with different inputs.

    2. Method Overriding (Runtime Polymorphism): Method overriding is a type of runtime polymorphism in which a subclass offers its own implementation of a method that is already defined in its superclass. The name, return type, and parameters of the overridden method in the subclass are the same as those of the method in the superclass. The subclass method is said to override the superclass method, and the decision of which method to invoke is made at runtime based on the actual type of the object.

    Code flexibility and extensibility are enabled through polymorphism. It allows you to develop code that works with objects of different classes without being tied to a specific implementation. Polymorphism is an important notion in OOP because it encourages code reuse, modularity, and abstraction. It makes it easier to write flexible and manageable code by allowing objects to be treated consistently based on their common qualities or interfaces, independent of their unique type.


    Overloading and overriding are two key topics in object-oriented programming that require the use of methods in Java. Both approaches allow you to construct numerous versions of a method, but their usage and functionality differ.

    1. Method Overloading: Method overloading is the ability to specify numerous methods in the same class with the same name but different parameters. It enables you to give various methods for invoking a method based on the number, type, and order of the parameters passed. The following are key points about method overloading:

    - Overloaded methods must be named the same but have distinct parameter lists (number, type, or order of parameters).

    - Compile-time overloading is decided by the static type of the arguments.

    - Return types and access modifiers have no effect on method overloading.

    - Overloading is frequently used to give convenience and flexibility by providing various methods for performing comparable activities. Example:

    public class Calculator {
        public int add(int a, int b) {
            return a + b;
        }
    
        public double add(double a, double b) {
            return a + b;
        }
    }
        

    The 'add' method in the preceding example is overloaded to accept both 'int' and 'double' inputs. Based on the parameter types, the appropriate version of the method is chosen at compilation time.

    2. Method Overriding: Method overriding is the ability to give a distinct implementation of a method declared in a superclass in a subclass. It enables a subclass to override the functionality of an inherited method. The following are key points concerning method overriding:

    - In both the superclass and the subclass, overridden methods must have the same name, return type, and parameter list.

    - The overridden method's access level in the subclass cannot be more restrictive than the method's access level in the superclass.

    - At runtime, overriding is determined depending on the actual type of the object executing the method.

    - Overriding allows a subclass to tweak or extend the behavior inherited from the superclass by providing its own implementation of a method. Example:

    public class Animal {
        public void makeSound() {
            System.out.println("Animal makes a sound");
        }
    }
    
    public class Dog extends Animal {
        @Override
        public void makeSound() {
            System.out.println("Dog barks");
        }
    }
        

    The 'Dog' class in the preceding example overrides the'makeSound' method from the 'Animal' class and provides its own implementation. When the'makeSound' method is used on a 'Dog' object, the overridden method in the 'Dog' class is called.

    Method overloading and overriding are strong features that add to Java code's flexibility and extensibility. Overloading allows you to specify numerous methods with the same name but distinct parameter lists, whereas overriding allows a subclass to implement an inherited method in its own way. These characteristics improve code reuse, encourage abstraction, and allow for polymorphism in object-oriented programming.


    Java's implementation of polymorphism makes use of virtual methods and method overriding. This is how it goes:

    1. Inheritance: Polymorphism is based on the idea of inheritance, where a subclass may take on traits from a superclass. The subclass accesses the variables and methods of the superclass and extends it. This enables subclass objects to be handled similarly to their superclass counterparts.

    2. Method Overriding: Providing an alternative implementation for a method that is already specified in the superclass is known as method overriding. A subclass must declare a method with the same name, return type, and parameter list as the superclass's method in order to override it. Although it is not required, the '@Override' annotation is used to indicate that a method is meant to override a superclass method.

    3. Virtual Methods: In Java, all non-static methods are automatically categorized as virtual methods. The Java runtime determines the actual type of the object during runtime when a method is called on it. It then searches the class hierarchy for the method's most particular implementation, starting with the object's actual type and working its way up through the inheritance structure.

    4. Dynamic Method Dispatch: Dynamic method dispatch refers to the process of deciding the precise implementation of a method during runtime. It enables you to use a reference to the superclass to call a method that has been overridden and is specified in the subclass. Instead of using the specified type of the reference, the Java runtime resolves the method call using the actual type of the object the reference is pointing to.

    Here is an illustration of how polymorphism functions in Java:

    class Animal {
        public void makeSound() {
            System.out.println("Animal makes a sound");
        }
    }
    
    class Dog extends Animal {
        @Override
        public void makeSound() {
            System.out.println("Dog barks");
        }
    }
    
    class Cat extends Animal {
        @Override
        public void makeSound() {
            System.out.println("Cat meows");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Animal animal1 = new Dog(); // Create a Dog object and assign it to an Animal reference
            Animal animal2 = new Cat(); // Create a Cat object and assign it to an Animal reference
    
            animal1.makeSound(); // Output: "Dog barks"
            animal2.makeSound(); // Output: "Cat meows"
        }
    }
        

    In the aforementioned example, the 'Dog' and 'Cat' classes are subclasses that override the'makeSound' method, with the 'Animal' class serving as the superclass. We can allocate objects from subclasses to variables with the superclass type thanks to polymorphism. These variables are used to call the'makeSound' method, which then executes the method's actual implementation in the corresponding subclass.

    Java code can be flexible and extensible thanks to polymorphism, which enables objects of many classes to be viewed as belonging to a single superclass. It encourages abstraction, code reuse, and the development of more flexible, generic code that can manage a variety of object kinds.


    In Java, polymorphism and overriding are related ideas, but they serve different functions and have different meanings. The distinction between polymorphism and overriding is as follows:

    Polymorphism: Polymorphism is a fundamental concept in object-oriented programming which permits objects of distinct classes to be considered as objects of a shared superclass or interface. It gives developers the ability to design code that interacts with objects of various types, enabling flexibility and extension. Method overloading and method overriding are used to create polymorphism.

    Method Overriding: Method overriding is a type of polymorphism in which a subclass offers its own implementation of a method that is already defined in its superclass. The name, return type, and parameter list of the method in the subclass are the same as those of the method in the superclass. The actual type of the object at runtime determines the implementation of the method that is executed when a method is called on an object. The '@Override' annotation is used to declare the method in the subclass in order to override it.

    Differences between overriding and polymorphism

    1. Definition

    - Polymorphism: The capacity to regard objects of several kinds as belonging to a single superclass is known as polymorphism.

    - Overriding: The ability to propose an alternative implementation of a method that is already specified in a subclass's superclass is known as overriding.

    2. Function:

    - Polymorphism: Polymorphism facilitates more general and flexible code writing by enabling objects of various classes to be treated consistently based on their shared traits.

    - Overriding: Overriding enables a subclass to offer a unique implementation of a method to alter or expand the superclass-inherited behavior.

    3. Application:

    - Polymorphism: Polymorphism, which is accomplished by method overriding (and method overloading), is used to construct code that can operate on objects of various types, allowing for flexibility and extension.

    - Overriding: When a subclass wishes to offer its own implementation of a method inherited from the superclass, overriding is expressly employed.

    4. Inheritance:

    - Polymorphism is based on the idea of inheritance, where a subclass receives traits and traits from a superclass.

    - Overriding: An inheritance feature known as overriding allows a subclass to offer its own implementation of a method that was inherited from the superclass.

    In conclusion, the ability to treat objects of various kinds as though they were members of a single superclass is referred to as polymorphism. On the other hand, overriding is a particular kind of polymorphism that entails giving a method in a subclass a different implementation. In Java, overriding is one method to implement polymorphism.


    Inheritance is a key mechanism in object-oriented programming that plays a crucial role in achieving polymorphism in Java. Polymorphism allows objects of different classes to be treated as objects of a common superclass or interface. Inheritance enables code reuse and provides a way to define a hierarchy of classes with increasing levels of specialization.

    Here's how inheritance is used in polymorphism in Java:

    1. Superclass and Subclass:

    Inheritance involves the creation of a superclass (also called a base class or parent class) and one or more subclasses (also called derived classes or child classes). The superclass contains common attributes and behaviors that are shared by the subclasses. The subclasses inherit these attributes and behaviors from the superclass.

    2. Code Reuse:

    Inheritance allows the subclass to reuse the code from the superclass. By inheriting the attributes and methods of the superclass, the subclass automatically gains access to them without having to redefine them. This promotes code reuse and reduces redundancy.

    3. Method Overriding:

    Inheritance enables the subclass to override methods inherited from the superclass. Method overriding is a form of polymorphism where the subclass provides its own implementation of a method that is already defined in the superclass. By overriding a method, the subclass can customize or extend the behavior inherited from the superclass. This allows different subclasses to have different implementations of the same method.

    4. Inheritance Hierarchy:

    Inheritance allows the creation of an inheritance hierarchy, where classes are organized in a hierarchical structure based on their level of specialization. Each subclass inherits the attributes and methods of its immediate superclass, as well as all the superclasses above it in the hierarchy. This creates a chain of classes that can be treated uniformly based on their common superclass.

    5. Polymorphic Behavior:

    Inheritance, along with method overriding, enables polymorphic behavior. Polymorphism allows objects of different classes, but with a common superclass, to be treated interchangeably. This means that a method defined in the superclass can be invoked on objects of the superclass or any of its subclasses. The actual implementation of the method that is executed is determined by the actual type of the object at runtime.

    In summary, inheritance is used in polymorphism in Java to establish a hierarchy of classes, reuse code, and allow subclasses to inherit methods and attributes from a superclass. This enables code flexibility and extensibility by allowing objects of different classes to be treated as objects of a common superclass and enables method overriding to customize or extend the behavior inherited from the superclass. Inheritance plays a critical role in achieving polymorphism and promoting code reuse in object-oriented programming.


    Polymorphism and inheritance are two fundamental concepts in object-oriented programming, but they have distinct meanings and purposes. Here's a detailed explanation of the difference between polymorphism and inheritance:

    Polymorphism:

    Polymorphism is the ability of objects of different classes to be treated as objects of a common superclass or interface. It allows you to write code that can work with objects of different types, providing flexibility and extensibility. Polymorphism is achieved through method overriding and method overloading.

    Key points about polymorphism:

    - Polymorphism allows objects of different classes to be treated uniformly based on their common characteristics or interfaces.

    - Polymorphism enables the same method to be called on different objects, resulting in different behaviors depending on the actual type of the object at runtime.

    - Polymorphism promotes code reusability, abstraction, and the ability to write more generic and flexible code that can handle different types of objects.

    Inheritance:

    Inheritance is a mechanism in object-oriented programming that allows a class to inherit properties and behaviors from another class, known as the superclass or parent class. It enables code reuse, abstraction, and the creation of a hierarchy of classes with increasing levels of specialization.

    Key points about inheritance:

    - Inheritance allows a subclass to inherit attributes and methods from its superclass, gaining access to their implementation without having to redefine them.

    - Inheritance establishes an "is-a" relationship between classes, where a subclass is a more specialized version of the superclass.

    - Inheritance promotes code reuse, modularity, and the ability to define common attributes and behaviors in a superclass and specialize them in subclasses.

    Differences between Polymorphism and Inheritance:

    1. Definition:

    - Polymorphism: Polymorphism refers to the ability of objects of different types to be treated as objects of a common superclass or interface.

    - Inheritance: Inheritance is the mechanism that allows a class to inherit properties and behaviors from another class.

    2. Purpose:

    - Polymorphism: Polymorphism enables code to be written in a more general and flexible manner, allowing objects of different classes to be processed uniformly based on their common characteristics.

    - Inheritance: Inheritance enables code reuse, modularity, and specialization by allowing subclasses to inherit and extend the attributes and behaviors of a superclass.

    3. Usage:

    - Polymorphism: Polymorphism is used to write code that can work with objects of multiple types, providing flexibility and extensibility.

    - Inheritance:Inheritance is used to establish an "is-a" relationship between classes, promote code reuse, and define a hierarchy of classes with increasing levels of specialization.

    4. Relationship:

    - Polymorphism: Polymorphism can be achieved without inheritance by implementing interfaces or using method overloading. It focuses on the behavior of objects.

    - Inheritance: Inheritance is a mechanism that enables code reuse and specialization by creating a hierarchy of classes. It focuses on the structure and hierarchy of classes.

    In summary, polymorphism and inheritance are related concepts in object-oriented programming, but they serve different purposes. Polymorphism allows objects of different classes to be treated uniformly based on their common characteristics, while inheritance enables code reuse, modularity, and the creation of a hierarchy of classes. Polymorphism is achieved through method overriding and overloading, while inheritance establishes an "is-a" relationship between classes. Both concepts contribute to the flexibility, extensibility, and maintainability of object-oriented code.


    A real-life example of polymorphism can be found in the concept of a shape. In the field of geometry, a shape can have various forms such as a circle, square, or triangle. These different shapes exhibit polymorphic behavior when we perform operations like calculating their area or perimeter.

    Let's consider an example:

    Suppose we have a superclass called "Shape" that defines a method named "calculateArea()". This method is overridden by its subclasses, such as "Circle", "Square", and "Triangle", which provide their own implementations of the "calculateArea()" method.

    abstract class Shape {
        public abstract double calculateArea();
    }
    
    class Circle extends Shape {
        private double radius;
        
        public Circle(double radius) {
            this.radius = radius;
        }
        
        @Override
        public double calculateArea() {
            return Math.PI * radius * radius;
        }
    }
    
    class Square extends Shape {
        private double sideLength;
        
        public Square(double sideLength) {
            this.sideLength = sideLength;
        }
        
        @Override
        public double calculateArea() {
            return sideLength * sideLength;
        }
    }
    
    class Triangle extends Shape {
        private double base;
        private double height;
        
        public Triangle(double base, double height) {
            this.base = base;
            this.height = height;
        }
        
        @Override
        public double calculateArea() {
            return 0.5 * base * height;
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Shape circle = new Circle(5);
            Shape square = new Square(4);
            Shape triangle = new Triangle(3, 6);
            
            System.out.println("Area of circle: " + circle.calculateArea());
            System.out.println("Area of square: " + square.calculateArea());
            System.out.println("Area of triangle: " + triangle.calculateArea());
        }
    }
    

    In this example, the superclass "Shape" declares the abstract method "calculateArea()", which is implemented differently in each of its subclasses. The "Circle" class calculates the area based on the radius, the "Square" class calculates the area based on the side length, and the "Triangle" class calculates the area based on the base and height.

    When we create objects of these subclasses and invoke the "calculateArea()" method, polymorphism allows us to treat them as objects of the common superclass "Shape". The actual implementation of the method that is executed is determined by the type of the object at runtime. This enables us to perform the area calculation specific to each shape without explicitly knowing their exact types.

    By leveraging polymorphism, we can write code that operates on objects of different shapes in a generalized manner. This flexibility allows us to extend the system by adding new shapes without modifying the existing code. Polymorphism promotes code reusability, abstraction, and the ability to write more flexible and maintainable code that can handle different types of objects with a common interface.


    Polymorphism is used in programming languages like Java for several reasons, as it provides a range of benefits and enables flexible and extensible code. Here are some reasons why polymorphism is used:

    1. Code Reusability:

    Polymorphism allows the reuse of code. By defining common interfaces or superclasses, objects of different classes can be treated uniformly, simplifying code design and reducing redundancy. Polymorphism enables the writing of generic code that can handle various types of objects, promoting modular and reusable code.

    2. Flexibility and Extensibility:

    Polymorphism enables code to be written in a more general and flexible manner. By treating objects of different classes as objects of a common superclass or interface, the code becomes adaptable to different scenarios. New classes can be added to the system without modifying existing code, as long as they conform to the shared interface or inherit from the superclass.

    3. Abstraction and Encapsulation:

    Polymorphism supports the principles of abstraction and encapsulation. It allows the abstraction of common characteristics and behaviors into a superclass or interface, hiding the specific implementation details of each subclass. This abstraction simplifies the understanding of the code, reduces complexity, and promotes modular design.

    4. Code Maintenance:

    Polymorphism facilitates code maintenance. By centralizing common behaviors and operations in a superclass or interface, changes or bug fixes can be made at a single location, which then affects all the subclasses. This reduces the likelihood of introducing errors or inconsistencies during code modifications.

    5. Interface and API Design:

    Polymorphism is essential for designing interfaces and APIs. It allows the definition of a common set of methods that objects must implement, ensuring consistency and compatibility across different implementations. Polymorphism helps create modular and pluggable systems where objects can be easily substituted with others that adhere to the same interface.

    6. Run-Time Flexibility:

    Polymorphism enables run-time flexibility. During program execution, the actual type of an object may differ from its declared type, allowing for dynamic behavior based on the actual type of the object. This feature supports dynamic method dispatch, allowing the appropriate method implementation to be determined at run-time, based on the actual object being referenced.

    In summary, polymorphism is used in programming to achieve code reusability, flexibility, extensibility, abstraction, encapsulation, and modular design. By treating objects of different classes as objects of a common superclass or interface, polymorphism promotes the writing of generic, adaptable, and maintainable code. It allows for the creation of pluggable systems, simplifies code maintenance, and supports dynamic behavior at runtime. Polymorphism is a key concept in object-oriented programming and a powerful tool for developing scalable and robust applications.


    Identifying polymorphism in code requires examining the relationships and behavior of objects. Here are some ways to identify polymorphism:

    1. Common Interface or Inheritance:

    Polymorphism often involves objects that share a common interface or inherit from the same superclass. Look for classes that implement an interface or extend a superclass. This indicates that the objects can be treated uniformly based on their shared characteristics.

    2. Method Overriding:

    Polymorphism involves method overriding, where a subclass provides its own implementation of a method that is already defined in its superclass. Look for methods with the same name, return type, and parameter list in different classes. If the behavior of the method varies depending on the type of the object being referenced, it indicates polymorphism.

    3. Object Substitution:

    Polymorphism allows objects of different types to be substituted for each other when they share a common superclass or interface. Look for code where objects are referenced using a superclass or interface type, rather than their specific class types. If the code works correctly with different types of objects, it suggests polymorphism.

    4. Method Invocations:

    Polymorphism can be observed when invoking methods on objects of different types. If the same method name is used across different objects and the behavior varies based on the object's type, it indicates polymorphism. Look for code that invokes methods on objects and observe how the behavior is determined at runtime.

    5. Code Reusability:

    Polymorphism promotes code reusability by allowing objects of different types to be treated uniformly. Look for situations where code can be written in a generic manner that can handle various types of objects. If the same code can be used with different objects without modifications, it suggests the presence of polymorphism.

    6. Dynamic Binding:

    Polymorphism involves dynamic binding, where the appropriate method implementation is determined at runtime based on the actual type of the object being referenced. Look for scenarios where the decision of which method implementation to invoke is made at runtime rather than compile-time. This indicates polymorphism.

    By analyzing the code and considering these aspects, you can identify polymorphism in action. Polymorphism allows for code flexibility, extensibility, and the ability to work with objects of different types in a unified manner. It promotes code reuse and simplifies code maintenance by designing systems that can accommodate new types of objects without modifying existing code.


    While polymorphism in Java offers many advantages, it also has a few disadvantages that should be considered. Here are some of the disadvantages of polymorphism:

    1. Performance Overhead:

    Polymorphism can introduce a slight performance overhead compared to direct method invocations. During runtime, the JVM needs to determine the actual type of the object and then dynamically bind the appropriate method implementation. This process incurs a small cost in terms of time and resources. However, in most cases, the performance impact is negligible and does not significantly affect the overall system performance.

    2. Limited Access to Specific Features:

    Polymorphism treats objects of different types as objects of a common superclass or interface. While this allows for code flexibility, it also means that specific features and behaviors unique to specific subclasses may not be accessible when working with objects in a polymorphic manner. If there is a need to access subclass-specific methods or attributes, downcasting or explicit type checking is required, which can make the code less clean and introduce potential errors.

    3. Difficulty in Understanding Code Flow:

    Polymorphism can sometimes make the code more difficult to understand and trace. When invoking a method on a polymorphic reference, it may not be immediately clear which implementation of the method will be executed. The actual implementation is determined at runtime based on the object's type, which may require examining multiple classes and their relationships to understand the code flow. This can make debugging and maintenance more challenging, especially in complex systems.

    4. Abstraction Complexity:

    Polymorphism relies on abstraction and inheritance to achieve its benefits. However, managing complex hierarchies of classes and their relationships can become challenging. As the number of subclasses and interfaces increases, the codebase can become more complex and harder to maintain. Careful design and planning are required to strike a balance between abstraction and practicality.

    5. Potential for Incorrect Behavior:

    Polymorphism allows objects of different types to be treated uniformly, which can lead to unexpected behavior if not handled properly. If the subclass overrides a method in a way that changes its behavior significantly, it can produce unexpected results when invoked through a polymorphic reference. Care must be taken to ensure that the behavior of the overridden method remains consistent and adheres to the principle of substitutability.

    Despite these disadvantages, polymorphism is a powerful concept in Java and object-oriented programming. While the mentioned drawbacks should be considered, they are generally outweighed by the benefits polymorphism provides in terms of code reuse, flexibility, and maintainability. With proper design and understanding of its implications, polymorphism can greatly enhance the modularity and extensibility of Java applications.


    In general, it is difficult to make a definitive statement about the speed of polymorphism since performance can vary depending on the specific context and implementation. However, there are a few factors to consider when evaluating the speed of polymorphism:

    1. Method Dispatch Mechanism:

    Polymorphism involves dynamic method dispatch, where the appropriate method implementation is determined at runtime based on the actual type of the object being referenced. This dynamic dispatch introduces a slight overhead compared to direct method invocations. The JVM needs to perform a lookup to find the correct method implementation, which incurs a small performance cost.

    2. Virtual Method Tables:

    In object-oriented languages like Java, virtual method tables (VMTs) are often used to implement dynamic dispatch. VMTs are data structures that store the addresses of the virtual methods in a class hierarchy. When a method is invoked on a polymorphic reference, the JVM consults the VMT to find the appropriate method implementation. Accessing the VMT incurs a small cost in terms of memory and CPU cycles.

    3. Level of Inheritance Hierarchy:

    The depth of the inheritance hierarchy can affect the speed of polymorphism. If there are many levels of inheritance and the hierarchy is complex, the dynamic dispatch process may require more steps to determine the correct method implementation. This can result in a slight performance impact.

    4. JVM Optimization:

    Modern JVMs are equipped with advanced optimization techniques, such as Just-In-Time (JIT) compilation and inline caching, that can improve the performance of polymorphic code. These optimizations can eliminate or minimize the overhead associated with dynamic dispatch by dynamically recompiling and optimizing the code at runtime. However, the effectiveness of these optimizations can vary depending on the specific JVM implementation and configuration.

    It's important to note that the performance impact of polymorphism is typically minimal and often negligible for most applications. The overhead introduced by dynamic dispatch is usually outweighed by the benefits of code reusability, flexibility, and maintainability provided by polymorphism. Moreover, the performance of an application is influenced by various factors beyond polymorphism, such as algorithm efficiency, I/O operations, database access, and network latency.

    When it comes to performance optimization, it is generally recommended to focus on algorithmic improvements, data structures, and efficient coding practices rather than trying to optimize polymorphism alone. Writing clean and maintainable code that accurately represents the problem domain should be the primary goal, and any performance optimizations should be based on profiling and identifying actual bottlenecks in the application.


    Covariant return type is a feature in Java that allows a subclass to override a method in the superclass and change the return type to a more specific or derived type. In other words, it allows the return type of the overridden method in the subclass to be a subtype of the return type in the superclass.

    To understand covariant return type, consider a class hierarchy where a subclass inherits a method from its superclass. Normally, when overriding a method, the return type in the subclass must be the same as, or a subtype of, the return type in the superclass. However, with covariant return types, the return type in the subclass can be a more specific type.

    Here's an example to illustrate covariant return type:

    class Shape {
        public Shape getShape() {
            return new Shape();
        }
    }
    
    class Circle extends Shape {
        @Override
        public Circle getShape() {
            return new Circle();
        }
    }
    

    In this example, the superclass `Shape` has a method `getShape()` that returns a `Shape` object. The subclass `Circle` overrides the `getShape()` method and changes the return type to `Circle`. This is possible because `Circle` is a subtype of `Shape`.

    Using covariant return types provides flexibility when working with inheritance and allows subclasses to provide more specific return types without violating the rules of method overriding. It enables polymorphism by allowing code to work with objects based on their declared types, even if the actual objects are of different types.

    It's important to note that covariant return types are only applicable to the return type of a method and not to the method parameters or method overloading. The concept ensures that a more specific type can be returned when overriding a method, promoting code readability and better type safety.

    Covariant return types can be beneficial in scenarios where subclasses need to provide more specialized return types, allowing for more intuitive and expressive code. However, it's important to use covariant return types judiciously and consider the design implications to maintain consistency and avoid confusion in the codebase.

Logicmojo Learning Library