Polymorphism in Java: Overloading, Overriding and Runnable Examples

Learn how Java chooses overloaded methods at compile time and overridden methods at runtime. Trace two complete programs, common traps and practice targets.

KnowledgeGate Team

Exam prep & CS education

Updated 22 Oct 20266 min read

Inheritance syntax is easy to copy, but predicting which Java method runs when a parent reference points to a child object is harder. Overloading is selected at compile time, while overriding is dispatched at runtime. Trace a polymorphic call by checking the reference's compile-time type, the object's runtime type, and the method signature.

Polymorphism in Java: one name, more than one behaviour

Polymorphism means one operation can take more than one form. Three facts control a method call: the reference's compile-time type, the object's runtime type, and the method signature selected for the call.

Consider Shape shape = new Circle(3);. The reference type is Shape, the runtime object is Circle, and an eligible overridden area() method executes from Circle.

Point

Method overloading

Method overriding

Decision time

Compile time

Runtime, after signature selection

Mechanism

Same name, different parameter lists

Child supplies a compatible implementation

Inheritance required

No

Yes, through a superclass or interface

Binding

Static or early binding

Dynamic dispatch for eligible instance methods

The compiler chooses an overload from the method name and parameter list. Changing only the return type does not create a valid overload. OOP Basics in Java: Classes, Objects and the Four Pillars connects classes, objects, encapsulation and all four pillars. Polymorphism questions narrow the decision to overload selection at compile time and eligible override dispatch at runtime.

Compile-time polymorphism in Java: method overloading

Methods in Java: Syntax, Parameters, Return Values and Examples covers declarations, parameters and return rules. Polymorphism adds a narrower question: which same-named signature accepts the supplied arguments?

java
public class DurationCalculator {
    static int totalMinutes(int hours) {
        return hours * 60;
    }

    static int totalMinutes(int hours, int extraMinutes) {
        return hours * 60 + extraMinutes;
    }

    public static void main(String[] args) {
        System.out.println("Two hours = " + totalMinutes(2) + " minutes");
        System.out.println("Two hours 35 minutes = "
            + totalMinutes(2, 35) + " minutes");
    }
}

Exact output:

Code
Two hours = 120 minutes
Two hours 35 minutes = 155 minutes

The call totalMinutes(2) supplies one int, so the compiler selects totalMinutes(int) and computes 2 * 60 = 120. The call totalMinutes(2, 35) supplies two int arguments, so it selects totalMinutes(int, int) and computes 2 * 60 + 35 = 155.

An overload may change parameter count, parameter types or parameter order. A return-type-only change is invalid because the call would still have the same signature.

Runtime polymorphism in Java: overriding and dynamic dispatch

Runtime polymorphism needs a superclass or interface that declares an instance method, a subclass that supplies a compatible implementation, and a parent-typed reference holding the child object. At compile time, Java checks that the method exists on the reference type. At runtime, it chooses the most specific overridden implementation on the actual object.

An override keeps the same method name and parameter list, uses a compatible return type, does not weaken access, and applies to an eligible instance method. Add @Override so the compiler catches a misspelt name or mismatched signature.

For the zero-argument area() call, the compiler accepts the signature declared on Shape. At runtime, a Rectangle object executes Rectangle.area(), while a Circle object executes Circle.area().

Java runtime polymorphism worked example: calculate two shape areas

Save this complete program as Main.java:

java
import java.util.Locale;

abstract class Shape {
    abstract double area();
}

class Rectangle extends Shape {
    private final double width;
    private final double height;

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

    @Override
    double area() {
        return width * height;
    }
}

class Circle extends Shape {
    private final double radius;

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

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

public class Main {
    public static void main(String[] args) {
        Shape[] shapes = {
            new Rectangle(8, 5),
            new Circle(3)
        };

        double total = 0;
        for (Shape shape : shapes) {
            double current = shape.area();
            System.out.printf(
                Locale.ROOT,
                "%s area = %.2f%n",
                shape.getClass().getSimpleName(),
                current
            );
            total += current;
        }

        System.out.printf(Locale.ROOT, "Total area = %.2f%n", total);
    }
}

For Rectangle(8, 5), dispatch reaches Rectangle.area() and returns 8 * 5 = 40.00. For Circle(3), it reaches Circle.area() and returns pi * 3 * 3 = 9pi, approximately 28.2743, which prints as 28.27. The unrounded sum is approximately 40 + 28.2743 = 68.2743, which prints as 68.27.

Code
Rectangle area = 40.00
Circle area = 28.27
Total area = 68.27

The loop is genuinely polymorphic. Its source knows each element only as Shape, yet the two identical shape.area() calls run different implementations.

Runtime dispatch: shapes[0] runs Rectangle.area() for 40.00 and shapes[1] runs Circle.area() for 28.27, totalling 68.27.

Polymorphism traps in Java: fields, static methods and unsafe casts

Fields and static methods do not use runtime dispatch. The OOPs Concepts in Java worked interview trace owns field hiding and compile-time binding; the shape program here owns overload selection and overridden instance methods. With Parent ref = new Child(), ref.value is chosen from Parent, Parent.tag() calls the static method on Parent, and only an overridden ref.show() is selected from Child.

Constructors are not overridden, final methods cannot be overridden, and private methods are not inherited as override candidates. A cast introduces another trap: Shape shape = new Rectangle(8, 5); ((Circle) shape).area(); compiles because Circle and Rectangle share the Shape hierarchy, but throws ClassCastException because the object is a Rectangle. Check the actual type before downcasting.

Method dispatch is separate from reference comparison. See String handling in Java for the distinct == and equals() rules.

Polymorphism in Java questions: trace overload selection before override dispatch

Trace this assessment-style snippet:

java
class A {
    void print(int x) {
        System.out.print("A" + x);
    }
}

class B extends A {
    @Override
    void print(int x) {
        System.out.print("B" + (x + 1));
    }

    void print(double x) {
        System.out.print("D" + x);
    }
}

A ref = new B();
ref.print(4);

The exact output is B5. First, compile-time type A makes print(int) the selected signature for argument 4. Then runtime object B dispatches that signature to B.print(int), which computes 4 + 1 = 5.

What happens for ref.print(4.0)? It is a compile-time error, not D4.0. Overload candidates come from compile-time type A, which does not declare print(double).

For any tracing question, record the reference type, list its callable signatures, select one from the argument types, record the runtime object, and locate the most specific eligible override.

Polymorphism in Java exercises: extend the examples with exact targets

  1. Add Triangle extends Shape with base = 10 and height = 4. Its method returns 0.5 * base * height = 0.5 * 10 * 4 = 20.00. Append it to the array. The same loop should print Triangle area = 20.00 and change the total from 68.27 to 88.27.

  2. Add static int totalMinutes(int days, int hours, int extraMinutes) to DurationCalculator, returning days * 24 * 60 + hours * 60 + extraMinutes. Calling totalMinutes(1, 2, 35) computes 1 * 24 * 60 + 2 * 60 + 35 = 1595. This is compile-time overloading, while the triangle exercise is runtime overriding.

Self-check: why does the array loop accept Triangle without an if? Why can changing the declared reference type change visible overloads even when the runtime object stays the same?

Polymorphism in Java: the short version and next step

Overloading selects a signature at compile time. Overriding supplies child behaviour at runtime. Only eligible instance methods dispatch dynamically. Parent references let one loop work across multiple child objects.

The key checkpoints are 120 and 155 for overload selection, then 40.00, 28.27 and 68.27 for runtime dispatch. If you need the surrounding sequence from Java basics through advanced concepts, MCQs and coding questions, continue with the Java course. If polymorphism was your only gap, implement Triangle(10, 4) and verify 88.27 before moving on.