Polymorphism: Compile-time vs Run-time

Difficulty: Beginner

Question

What is polymorphism? Explain its types with examples and tell me how a parent reference can call a child method.

Answer

Polymorphism literally means many forms, and in programming it means that the same operation can behave differently depending on the object it acts upon. Let me build the intuition first and then give you the precise vocabulary.

Consider a remote control with a single power button. Point it at a TV and it turns the TV on, point it at an air conditioner and it turns the AC on. The button is the same, the actual behaviour depends on the target. In code, this is what lets you write a loop over a list of Animals, call speak() on each, and get a bark, a meow, or a roar without any if-else checking the type.

There are two kinds. Compile-time polymorphism, also called static polymorphism, is resolved by the compiler. The main form is method overloading: several methods with the same name but different parameter lists in the same class. The compiler looks at the argument types and picks the right one. Operator overloading in C++ is another form, while Java only supports it internally for string concatenation with plus.

Run-time polymorphism, also called dynamic polymorphism, is resolved when the program runs. It relies on method overriding: a subclass provides its own implementation of a method already defined in the parent, and when you call that method through a parent-type reference, the JVM looks at the actual object type and invokes the overridden version. This process is called dynamic method dispatch.

How does a parent reference hold a child object? That is called upcasting, and it is always safe because a Dog is an Animal. So Animal a = new Dog() compiles. The catch is that the compiler only lets you call methods that exist in the reference type Animal. But when you call speak(), which Animal declares and Dog overrides, the JVM runs Dog's version. If you want a Dog-only method like fetch(), you need to downcast: ((Dog) a).fetch(). A wrong downcast throws ClassCastException at run time, and instanceof is the guard against that.

Some rules worth memorising. Overloading is about different signatures; changing only the return type is not overloading and will not compile. Overriding requires the same signature, a return type that is the same or covariant, and an access level that is not more restrictive. Only instance methods take part in run-time polymorphism; fields, static methods, and private methods are resolved at compile time using the reference type.

Why does polymorphism matter for design? It is what makes code open for extension. You can add a new Animal subclass and the existing loop works without modification. It is also what powers frameworks: JDBC drivers, collection interfaces, and Spring beans all depend on callers using an abstract type while the implementation varies.

Include the caution that polymorphism through inheritance is one option; interfaces, generics (parametric polymorphism), and duck typing in Python provide other forms.

Code examples

Run-time polymorphism via overriding

class Animal {
    void speak() { System.out.println("..."); }
}
class Dog extends Animal {
    @Override void speak() { System.out.println("Woof"); }
}
class Cat extends Animal {
    @Override void speak() { System.out.println("Meow"); }
}

public class RuntimePoly {
    public static void main(String[] args) {
        Animal[] zoo = { new Dog(), new Cat(), new Animal() };
        for (Animal a : zoo) {
            a.speak();
        }
    }
}

The reference type is always Animal, the object type decides which speak() runs.

Compile-time polymorphism via overloading

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

public class OverloadDemo {
    public static void main(String[] args) {
        Calc c = new Calc();
        System.out.println(c.add(2, 3));
        System.out.println(c.add(2.5, 3.5));
        System.out.println(c.add(1, 2, 3));
    }
}

Key points

Concepts covered

polymorphism, overloading, overriding, upcasting