Java > Object-Oriented Programming (OOP) > Polymorphism > Static vs Dynamic Polymorphism

Static vs. Dynamic Polymorphism in Java

This example demonstrates static (compile-time) and dynamic (run-time) polymorphism in Java. Static polymorphism, also known as method overloading, is resolved at compile time. Dynamic polymorphism, also known as method overriding, is resolved at runtime.

Concepts Behind Static Polymorphism (Method Overloading)

Static polymorphism (method overloading) occurs when multiple methods in the same class have the same name but different parameters (different types or number of arguments). The compiler determines which method to call based on the arguments passed during the method call. This is resolved at compile time, hence the name 'static'.

Static Polymorphism Example

In this Calculator class, the add method is overloaded. There are three versions of the add method: one that takes two integers, one that takes two doubles, and one that takes three integers. The compiler chooses the correct add method to call based on the arguments provided in the main method.

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;
    }

    public static void main(String[] args) {
        Calculator calc = new Calculator();
        System.out.println("Adding two integers: " + calc.add(5, 10));
        System.out.println("Adding two doubles: " + calc.add(2.5, 3.7));
        System.out.println("Adding three integers: " + calc.add(1, 2, 3));
    }
}

Concepts Behind Dynamic Polymorphism (Method Overriding)

Dynamic polymorphism (method overriding) occurs when a subclass provides a specific implementation of a method that is already defined in its superclass. The method in the subclass must have the same name, return type, and parameters as the method in the superclass. Which method to call is determined at runtime based on the actual object type, even if it's referenced through a superclass reference. This is achieved through inheritance and the @Override annotation (recommended for clarity).

Dynamic Polymorphism Example

In this example, the Animal class has a makeSound method. The Dog and Cat classes extend the Animal class and override the makeSound method to provide their specific implementations. In the main method, even though animal2 and animal3 are declared as type Animal, the correct makeSound method is called at runtime based on the actual object type (Dog and Cat respectively). The @Override annotation indicates that the method is overriding a method from the superclass.

class Animal {
    public void makeSound() {
        System.out.println("Generic animal sound");
    }
}

class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof!");
    }
}

class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Meow!");
    }
}

public class PolymorphismExample {
    public static void main(String[] args) {
        Animal animal1 = new Animal();
        Animal animal2 = new Dog();
        Animal animal3 = new Cat();

        animal1.makeSound(); // Output: Generic animal sound
        animal2.makeSound(); // Output: Woof!
        animal3.makeSound(); // Output: Meow!
    }
}

Real-Life Use Case

Consider a graphical user interface (GUI) where different types of shapes (e.g., circles, squares, triangles) inherit from a common Shape class. Each shape can have its own implementation of a draw method. When the GUI needs to draw a list of shapes, it can iterate through the list and call the draw method on each shape object. Due to dynamic polymorphism, the correct draw method for each shape will be called, ensuring that each shape is drawn correctly.

Best Practices

  • Use the @Override annotation when overriding methods to improve code readability and help the compiler catch errors.
  • Design your class hierarchies carefully to ensure that polymorphism is used effectively.
  • Avoid excessive method overloading, as it can make code harder to understand.

Interview Tip

Be prepared to explain the difference between static and dynamic polymorphism with examples. Understand how the compiler and runtime environment resolve method calls in each case.

When to Use Them

  • Use static polymorphism (method overloading) when you need to provide different implementations of a method that perform similar tasks but operate on different data types or a different number of arguments.
  • Use dynamic polymorphism (method overriding) when you want subclasses to provide their own specific implementations of a method defined in a superclass, allowing for flexible and extensible code.

Alternatives

While polymorphism is a powerful tool, alternative approaches may be considered in certain scenarios. For example, using interfaces and implementing them in different classes can achieve similar results as dynamic polymorphism. Functional programming concepts, like using function objects (lambdas) can also provide different behavior without inheritance in some cases.

Pros of Polymorphism

  • Code Reusability: Subclasses inherit the properties and behaviors of their superclasses.
  • Flexibility: Polymorphism allows you to write code that can work with objects of different classes in a uniform way.
  • Extensibility: You can easily add new classes to your system without modifying existing code.

Cons of Polymorphism

  • Complexity: Understanding and debugging polymorphic code can be more challenging than working with non-polymorphic code.
  • Potential Performance Overhead: Dynamic dispatch (determining the method to call at runtime) can introduce a small performance overhead compared to static dispatch (method call determined at compile time). However, in most applications, this overhead is negligible.

FAQ

  • What is the difference between static and dynamic binding?

    Static binding (early binding) occurs at compile time, while dynamic binding (late binding) occurs at runtime. Method overloading uses static binding, and method overriding uses dynamic binding.
  • How does dynamic polymorphism improve code maintainability?

    Dynamic polymorphism allows you to add new functionality without modifying existing code. This makes your code more maintainable and less prone to errors.
  • What is the role of the @Override annotation?

    The @Override annotation is used to indicate that a method is overriding a method in its superclass. It's optional, but highly recommended as it helps the compiler catch errors if the method signature doesn't match the superclass method.