Popular Searches
Popular Course Categories
Popular Courses

Understanding Runtime and Compile-Time Polymorphism

Understanding Runtime and Compile-Time Polymorphism

Encapsulation, Inheritance, and Polymorphism

Understanding Runtime and Compile-Time Polymorphism in Java

Course: Core Java Training

Topic: Understanding Runtime and Compile-Time Polymorphism

Course Link: Core Java Training Course

Demo Link: Register for a Course Demo

Website: JustAcademy


1. Introduction to Polymorphism in Java

Polymorphism is one of the four fundamental principles of object-oriented programming (OOP), along with inheritance, encapsulation, and abstraction. The word polymorphism means "many forms." In Java, polymorphism allows the same method name or common reference to be used in different ways depending on the method parameters or the actual object involved.

Polymorphism helps developers create flexible, reusable, maintainable, and extensible applications. Instead of writing separate logic for every variation of an operation, developers can use a common method name or superclass reference to represent different behaviors.

Java commonly demonstrates two forms of polymorphism:

  • Compile-time polymorphism: Commonly achieved through method overloading.
  • Runtime polymorphism: Commonly achieved through method overriding and dynamic method dispatch.

2. What Is Compile-Time Polymorphism?

Compile-time polymorphism is a form of polymorphism in which the compiler determines which overloaded method to call based on the method name, argument count, argument types, and applicable method-resolution rules.

It is also known as static polymorphism or static binding in introductory Java terminology. Method overloading is its most common example.

Key Characteristics

  • It is commonly implemented using method overloading.
  • Methods share the same name but have different parameter lists.
  • The compiler selects the applicable overloaded method during compilation.
  • It can make APIs easier to use by allowing one method name to support different input types or quantities.
  • Inheritance is not required for basic method overloading.

3. Understanding Method Overloading

Method overloading occurs when a class contains multiple methods with the same name but different parameter lists. The methods may differ in the number of parameters, parameter types, or parameter order.

Example 1: Basic Method Overloading

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

    int add(int a, int b, int c) {
        return a + b + c;
    }

    double add(double a, double b) {
        return a + b;
    }
}

public class Main {
    public static void main(String[] args) {
        Calculator calculator = new Calculator();

        System.out.println(calculator.add(10, 20));
        System.out.println(calculator.add(10, 20, 30));
        System.out.println(calculator.add(5.5, 4.5));
    }
}

Output

30
60
10.0

Explanation

  • add(int, int) accepts two integer arguments.
  • add(int, int, int) accepts three integer arguments.
  • add(double, double) accepts two double arguments.
  • The compiler selects the appropriate overload based on the supplied arguments and Java's method-resolution rules.

4. Rules of Method Overloading

To overload a method, its parameter list must be different from the other method's parameter list.

Method Variation Valid Overloading? Explanation
Different number of parameters Yes For example, show(int) and show(int, int).
Different parameter types Yes For example, display(int) and display(String).
Different parameter order Yes For example, print(int, String) and print(String, int).
Different return type only No Changing only the return type does not create a valid overload.
Different parameter names only No Parameter names do not distinguish method signatures for overloading.

Example 2: Overloading with Different Parameter Types

class Display {
    void show(int number) {
        System.out.println("Integer: " + number);
    }

    void show(String text) {
        System.out.println("Text: " + text);
    }

    void show(double number) {
        System.out.println("Decimal: " + number);
    }
}

public class Main {
    public static void main(String[] args) {
        Display display = new Display();

        display.show(100);
        display.show("Java");
        display.show(25.5);
    }
}

Output

Integer: 100
Text: Java
Decimal: 25.5

5. Compile-Time Polymorphism with Constructors

Java also supports constructor overloading. A class can define multiple constructors with different parameter lists to initialize objects in different ways. The compiler determines which constructor is applicable when an object is created.

class Student {
    String name;
    int age;

    Student() {
        name = "Unknown";
        age = 0;
    }

    Student(String name) {
        this.name = name;
        age = 18;
    }

    Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    void display() {
        System.out.println(name + " - " + age);
    }
}

public class Main {
    public static void main(String[] args) {
        Student s1 = new Student();
        Student s2 = new Student("Amit");
        Student s3 = new Student("Priya", 21);

        s1.display();
        s2.display();
        s3.display();
    }
}

Output

Unknown - 0
Amit - 18
Priya - 21

Each constructor has a distinct parameter list. Java chooses the applicable constructor according to the arguments passed during object creation.

6. Advantages of Compile-Time Polymorphism

  • Readability: Related operations can share a meaningful method name.
  • Convenience: Callers can pass different supported argument combinations.
  • Code organization: Similar operations can be grouped under one method name.
  • Type safety: The compiler checks method applicability and argument compatibility.
  • Reusability: One class can provide several versions of a useful operation.

7. Limitations of Compile-Time Polymorphism

  • Too many overloaded methods can make an API difficult to understand.
  • Ambiguous overloads can cause compilation errors.
  • Overloading alone does not let a subclass replace an inherited method's implementation for dynamic dispatch.
  • Overloading decisions can be affected by compile-time argument types, widening conversions, boxing, and varargs rules.

8. What Is Runtime Polymorphism?

Runtime polymorphism is a form of polymorphism in which Java determines which overridden instance method implementation to execute at runtime based on the actual object's class.

It is also called dynamic polymorphism or dynamic method dispatch. It is commonly achieved through method overriding and inheritance, including implementations of interface methods.

Key Characteristics

  • It commonly uses inheritance or interface implementation.
  • A subclass provides an implementation of an inherited instance method.
  • A superclass reference can refer to an instance of a subclass.
  • For an overridden instance method, the implementation associated with the actual object is selected at runtime.
  • It supports flexible designs where different object types can be handled through a common abstraction.

9. Understanding Method Overriding

Method overriding occurs when a subclass provides its own implementation of an inherited instance method using a compatible method signature and return type. The overriding method cannot reduce the access level of the overridden method.

The @Override annotation is recommended because it allows the compiler to detect mistakes when a method is intended to override an inherited method.

Example 3: Basic Method Overriding

class Animal {
    void sound() {
        System.out.println("Animal makes a sound.");
    }
}

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

class Cat extends Animal {
    @Override
    void sound() {
        System.out.println("Cat meows.");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();

        dog.sound();
        cat.sound();
    }
}

Output

Dog barks.
Cat meows.

Explanation

  • Animal defines the common sound() method.
  • Dog overrides sound() to represent a dog's sound.
  • Cat overrides sound() to represent a cat's sound.
  • Calling sound() on each object executes that class's implementation.

10. Runtime Polymorphism Using a Parent Reference

One of the most important features of runtime polymorphism is that a superclass reference can refer to a subclass object. The reference's declared type determines which members are available at compile time, while the actual object's class determines the overridden instance method implementation executed at runtime.

class Animal {
    void sound() {
        System.out.println("Animal sound.");
    }
}

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

class Cat extends Animal {
    @Override
    void sound() {
        System.out.println("Cat meows.");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal;

        animal = new Dog();
        animal.sound();

        animal = new Cat();
        animal.sound();
    }
}

Output

Dog barks.
Cat meows.

The variable animal is declared as Animal, but it refers to a Dog object first and a Cat object second. Java invokes the overridden method associated with each actual object.

11. Dynamic Method Dispatch

Dynamic method dispatch is the mechanism Java uses to select an overridden instance method implementation at runtime. It enables runtime polymorphism.

How It Works

  1. A superclass defines an instance method.
  2. A subclass overrides that method.
  3. A superclass reference refers to an object of the subclass.
  4. The program invokes the method using the superclass reference.
  5. At runtime, Java selects the implementation belonging to the actual object's class.
Animal animal = new Dog();
animal.sound();

Here, animal has the declared type Animal, but the actual object is Dog. If Dog overrides sound(), Java executes Dog's version.

12. Runtime Polymorphism with Abstract Classes

An abstract class can define shared behavior and declare abstract methods that subclasses must implement. References of the abstract superclass type can then refer to different concrete subclass objects.

abstract class Shape {
    abstract double calculateArea();
}

class Circle extends Shape {
    double radius;

    Circle(double radius) {
        this.radius = radius;
    }

    @Override
    double calculateArea() {
        return Math.PI * radius * radius;
    }
}

class Rectangle extends Shape {
    double length;
    double width;

    Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    @Override
    double calculateArea() {
        return length * width;
    }
}

public class Main {
    public static void main(String[] args) {
        Shape shape1 = new Circle(3);
        Shape shape2 = new Rectangle(4, 5);

        System.out.printf("Circle area: %.2f%n",
                shape1.calculateArea());
        System.out.printf("Rectangle area: %.2f%n",
                shape2.calculateArea());
    }
}

Output

Circle area: 28.27
Rectangle area: 20.00

The same method call, calculateArea(), produces different results depending on whether the actual object is a Circle or Rectangle.

13. Runtime Polymorphism with Interfaces

Interfaces allow unrelated implementation classes to provide a common contract. Code can invoke interface methods without needing to know which concrete implementation it receives.

interface Payment {
    void pay(double amount);
}

class CreditCardPayment implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("Paid " + amount + " using credit card.");
    }
}

class UpiPayment implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("Paid " + amount + " using UPI.");
    }
}

public class Main {
    public static void main(String[] args) {
        Payment payment;

        payment = new CreditCardPayment();
        payment.pay(1500);

        payment = new UpiPayment();
        payment.pay(750);
    }
}

Output

Paid 1500.0 using credit card.
Paid 750.0 using UPI.

This example demonstrates how the same Payment interface can represent different payment implementations. A real payment system would also require appropriate validation, secure processing, and error handling.

14. Compile-Time vs Runtime Polymorphism

Feature Compile-Time Polymorphism Runtime Polymorphism
Common mechanism Method overloading Method overriding
Method selection Compiler resolves the applicable overload Overridden instance method is selected at runtime
Parameter list Must differ for overloads Must satisfy overriding rules
Inheritance required No Usually uses inheritance or interfaces
Reference type Argument and reference types can affect overload resolution Actual object type selects the overridden implementation
Typical binding terminology Static binding Dynamic binding
Example add(int, int) and add(double, double) Dog and Cat override sound()
Main benefit Convenient method APIs for different inputs Flexible behavior through common parent types or interfaces

15. Example Combining Overloading and Overriding

Java supports both forms in the same application. In the following example, the Animal class has overloaded methods, while Dog overrides one of its inherited methods.

class Animal {
    void eat() {
        System.out.println("Animal eats food.");
    }

    void eat(String food) {
        System.out.println("Animal eats " + food + ".");
    }

    void sound() {
        System.out.println("Animal makes a sound.");
    }
}

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

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();

        dog.eat();
        dog.eat("meat");
        dog.sound();
    }
}

Output

Animal eats food.
Animal eats meat.
Dog barks.

The eat() methods demonstrate overloading because their parameter lists differ. The sound() method demonstrates overriding because Dog provides a replacement implementation inherited from Animal.

16. Important Rules and Special Cases

  • Return type alone is not enough for overloading: Two methods cannot differ only by return type.
  • Overriding requires compatibility: The subclass method must follow Java's signature, return-type, and access rules.
  • Static methods are hidden, not overridden: Static method calls are resolved using the compile-time type and method-hiding rules.
  • Private methods are not overridden: A private method is not inherited as an overridable method by a subclass.
  • Final methods cannot be overridden: A subclass cannot override a method declared final.
  • Overload resolution uses compile-time information: Java chooses an applicable overload using the declared types of expressions and its method-resolution rules.
  • Overridden instance method dispatch uses the actual object: For ordinary overridable instance methods, the implementation is selected at runtime.

17. Common Mistakes to Avoid

  • Assuming that changing only the return type creates an overloaded method.
  • Confusing method overloading with method overriding.
  • Forgetting to use @Override when a method is intended to override a superclass method.
  • Trying to override a final or private method.
  • Assuming a superclass reference can directly access subclass-specific methods without an appropriate cast or another design approach.
  • Assuming static methods use runtime polymorphism in the same way as overridden instance methods.
  • Creating too many overloads with unclear or ambiguous argument combinations.
  • Using inheritance where composition or an interface would be a better fit.

18. Best Practices

  • Use method overloading when methods represent the same logical operation with different parameter lists.
  • Use method overriding when a subclass needs to specialize inherited behavior.
  • Use @Override to make your intention explicit and catch mistakes.
  • Design superclass and interface contracts carefully so that implementations behave consistently.
  • Prefer programming to interfaces when different implementations should be interchangeable.
  • Keep method names descriptive and overloaded versions easy to understand.
  • Test both the common behavior and the behavior specific to each implementation.
  • Use polymorphism to reduce repetitive conditional logic when object-specific behavior is naturally modeled by overridden methods.

19. Real-World Applications

Application Compile-Time Example Runtime Example
Calculator Overloaded add() methods for different input types Different calculator implementations override a common operation
Payment System Overloaded methods accept different supported input formats Card, UPI, and other payment classes implement a common payment contract
Notification System Overloaded notification methods accept different parameter combinations Email and SMS notification classes provide their own delivery behavior
Graphics Application Overloaded drawing methods accept different argument lists Circle and Rectangle override a common drawing or area method
Employee Management Overloaded search methods accept different search criteria Employee subclasses provide specialized calculation behavior

20. Practice Exercise 1: Compile-Time Polymorphism

Task: Create a class named Calculator with overloaded methods named multiply().

Requirements

  1. Create a method that multiplies two integers.
  2. Create another method that multiplies three integers.
  3. Create a method that multiplies two double values.
  4. Call all three methods from the main method.
  5. Display each result.

Expected Output

Product of two integers: 50
Product of three integers: 120
Product of decimal values: 10.0

21. Practice Exercise 2: Runtime Polymorphism

Task: Create a parent class named Employee and two subclasses named Developer and Manager.

Requirements

  1. Add a method named work() to Employee.
  2. Override work() in Developer and Manager.
  3. Create an Employee reference.
  4. Assign a Developer object to the reference and call work().
  5. Assign a Manager object to the same reference and call work().

Expected Output

Developer writes and tests code.
Manager plans work and coordinates the team.

22. Interview Questions and Answers

Q1. What is polymorphism in Java?

Answer: Polymorphism allows the same method name or common reference to represent different behaviors depending on parameters or the actual object.

Q2. What are the two commonly taught types of polymorphism in Java?

Answer: Compile-time polymorphism, commonly demonstrated by method overloading, and runtime polymorphism, commonly demonstrated by method overriding.

Q3. What is compile-time polymorphism?

Answer: It is polymorphism in which the compiler selects an applicable overloaded method based on compile-time information and Java's method-resolution rules.

Q4. What is runtime polymorphism?

Answer: It is polymorphism in which Java selects an overridden instance method implementation at runtime based on the actual object.

Q5. What is the difference between overloading and overriding?

Answer: Overloading uses the same method name with different parameter lists. Overriding occurs when a subclass supplies a compatible implementation of an inherited instance method.

Q6. Can we overload a method by changing only its return type?

Answer: No. The parameter list must differ; a return-type change alone does not create a valid overload.

Q7. Can static methods be overridden?

Answer: No. A static method declared in a subclass with the same signature hides the superclass's static method rather than overriding it.

Q8. What is dynamic method dispatch?

Answer: It is the runtime mechanism that selects the implementation of an overridden instance method based on the actual object.

Q9. Why is the @Override annotation useful?

Answer: It tells the compiler that a method is intended to override an inherited method, helping detect incorrect signatures and other mistakes.

Q10. Can runtime polymorphism work with interfaces?

Answer: Yes. A reference of an interface type can refer to different implementing objects, and overridden implementations can be selected at runtime.

Q11. Is inheritance required for method overloading?

Answer: No. A class can overload its own methods without extending another user-defined class.

Q12. What is the main benefit of runtime polymorphism?

Answer: It allows different object types to be handled through a common superclass or interface while executing their own implementations of shared methods.

23. Quick Revision Checklist

  • Polymorphism means "many forms."
  • Compile-time polymorphism is commonly achieved through method overloading.
  • Overloaded methods must have different parameter lists.
  • Changing only the return type does not create a valid overload.
  • Runtime polymorphism is commonly achieved through method overriding.
  • Overriding allows a subclass to specialize inherited instance behavior.
  • A superclass reference can refer to a subclass object.
  • The actual object determines which overridden instance method implementation runs.
  • Static methods are hidden rather than overridden.
  • The @Override annotation helps detect mistakes.
  • Interfaces can support runtime polymorphism.
  • Both forms improve the design and usability of Java applications when used appropriately.

24. Conclusion

Compile-time and runtime polymorphism are essential concepts in Java object-oriented programming. Compile-time polymorphism, usually demonstrated by method overloading, lets developers use the same method name for different parameter lists. Runtime polymorphism, commonly demonstrated by method overriding, allows different objects to provide specialized implementations of a shared method.

Understanding the difference between overload resolution and dynamic method dispatch helps developers write cleaner, more reusable, and extensible Java applications. Practice both concepts with small programs before applying them to larger projects.


Learn Core Java with JustAcademy

Strengthen your Java fundamentals, object-oriented programming knowledge, and practical coding skills with the following resources.

Additional Learning Resource: Oracle Java Tutorial: Polymorphism

Previous Next
whatsapp