# Polymorphism

Course: [Java](https://www.devtutorials.ai/learn/java) · Section: Object-Oriented Programming
Source: https://www.devtutorials.ai/learn/java/object-oriented-programming/polymorphism

> Use one name for many behaviours with overloading and overriding, @Override, dynamic method dispatch, and safe upcasting and downcasting.

Think about the word "open".

You open a door. You open a book. You open an app on your phone. The word is the same each time, but what actually happens is different. It depends on what you are opening.

Java works the same way. One method name can do different things, depending on the values you pass or the object you call it on.

In simple words: polymorphism means one name, many forms.

The word comes from Greek: *poly* means "many", and *morph* means "form".

## What You Will Learn

In this lesson, you will learn:

1. What polymorphism is, and why it makes code flexible.
2. How method overloading gives compile-time polymorphism.
3. How method overriding gives runtime polymorphism.
4. Why you should always use the `@Override` annotation.
5. How Java picks the right method while the program runs.
6. How upcasting and downcasting work, and how to downcast safely.
7. The differences between compile-time and runtime polymorphism.

## Start With a Working Example

Press **Run**.

```java runnable title="One call, three different results"
public class Main {
    public static void main(String[] args) {
        Animal[] zoo = { new Dog(), new Cat(), new Cow() };

        for (Animal a : zoo) {
            a.speak();
        }
    }
}

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

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

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

class Cow extends Animal {
    @Override
    void speak() {
        System.out.println("Cow says Moo");
    }
}
```

Expected output:

```text
Dog says Woof
Cat says Meow
Cow says Moo
```

The loop always calls `a.speak()`, and `a` is always an `Animal` variable. Yet each animal makes its own sound. Java runs the version that belongs to the real object.

### Try This

Add a `Duck` class that extends `Animal` and overrides `speak()`. Put a `new Duck()` in the array. The loop works without any change.

## Introduction to Polymorphism

**Polymorphism** means one name can have many forms.

In Java, it shows up in two ways:

| Kind | Also called | How it works | Decided |
|---|---|---|---|
| Compile-time polymorphism | Static polymorphism | Method overloading | When the code is compiled |
| Runtime polymorphism | Dynamic polymorphism | Method overriding | While the program runs |

Why it matters:

- **Flexible code.** One loop can handle many types of object.
- **Easy to extend.** A new class works with old code, as long as it keeps the same method names.
- **Less `if-else`.** You do not need checks like "if it is a dog, bark; if it is a cat, meow". Each object knows its own behaviour.

## Compile-Time Polymorphism

**Compile-time polymorphism** happens when the compiler decides which method to call, before the program runs.

It looks only at the method name and the arguments you pass.

### Method Overloading

**Method overloading** means several methods in the same class have the same name but different parameters.

You learned this in the Methods lesson. Here it is again, as a form of polymorphism.

```java runnable title="The compiler picks a version from the arguments"
public class Main {
    public static void main(String[] args) {
        Printer p = new Printer();

        p.print(42);
        p.print("Hello");
        p.print(3.5);
        p.print("Total", 250);
    }
}

class Printer {
    void print(int number) {
        System.out.println("Number: " + number);
    }

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

    void print(double value) {
        System.out.println("Decimal: " + value);
    }

    void print(String label, int value) {
        System.out.println(label + " = " + value);
    }
}
```

Expected output:

```text
Number: 42
Text: Hello
Decimal: 3.5
Total = 250
```

All four methods are called `print()`. The compiler looks at each call's arguments and chooses the matching version.

The choice is made when the code is compiled. That is why this is called **compile-time** polymorphism.

## Runtime Polymorphism

**Runtime polymorphism** happens when Java decides which method to call **while the program runs**, based on the actual object.

### Method Overriding

**Method overriding** means a child class writes its own version of a method it inherited from its parent.

The overriding method must have:

- the **same name** as the parent's method;
- the **same parameters**, in the same order;
- the same return type, or a child type of it;
- the same or **wider** access. A `public` method cannot become `private` in the child.

You cannot override `static`, `final` or `private` methods.

```java runnable title="Each child gives the inherited method its own body"
public class Main {
    public static void main(String[] args) {
        Payment[] payments = {
            new CardPayment(),
            new UpiPayment(),
            new CashPayment()
        };

        for (Payment p : payments) {
            p.pay(500);
        }
    }
}

class Payment {
    void pay(int amount) {
        System.out.println("Paying " + amount);
    }
}

class CardPayment extends Payment {
    @Override
    void pay(int amount) {
        System.out.println("Card: charged " + amount + " plus 2% fee = " + (amount + amount * 2 / 100));
    }
}

class UpiPayment extends Payment {
    @Override
    void pay(int amount) {
        System.out.println("UPI: sent " + amount + " with no fee");
    }
}

class CashPayment extends Payment {
    @Override
    void pay(int amount) {
        System.out.println("Cash: collect " + amount + " at delivery");
    }
}
```

Expected output:

```text
Card: charged 500 plus 2% fee = 510
UPI: sent 500 with no fee
Cash: collect 500 at delivery
```

The checkout code only calls `p.pay(500)`. It does not need to know which payment method the customer chose.

| | Overloading | Overriding |
|---|---|---|
| Where | In the same class | In a parent and a child class |
| Method name | Same | Same |
| Parameters | Must be different | Must be the same |
| Needs inheritance? | No | Yes |
| Decided | At compile time | At run time |
| Purpose | Same job, different inputs | Same job, different behaviour per type |

## @Override Annotation

An **annotation** is a note for the compiler, written with `@`.

**`@Override`** goes on the line above a method that is meant to override a parent method. It tells the compiler: "check that this really overrides something".

Without it, a small typo creates a **new** method instead of overriding the old one. The program still compiles, but the parent's version keeps running, and the bug is hard to find.

```java runnable expect-error title="@Override catches a misspelled method name"
public class Main {
    public static void main(String[] args) {
        Animal a = new Dog();
        a.speak();
    }
}

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

class Dog extends Animal {
    @Override
    void speek() {
        System.out.println("Woof");
    }
}
```

The compiler reports `method does not override or implement a method from a supertype`. The typo `speek` was caught straight away.

Without `@Override`, this program would compile and quietly print `Some sound`.

**Easy Way to Remember:** every time you override, write `@Override`. It costs one line and catches typos in names and parameters.

## Dynamic Method Dispatch

**Dynamic method dispatch** is how Java carries out runtime polymorphism.

Two types are involved whenever you use a variable:

- The **reference type** is the type of the variable: `Animal a`.
- The **object type** is the type of the object it points to: `new Dog()`.

When you call an overridden method, Java looks at the **object type**, not the reference type. It finds that object's own version and runs it.

The decision is made while the program runs, because the same variable might point to a different kind of object each time.

```java runnable title="The object type decides, not the variable type"
public class Main {
    public static void main(String[] args) {
        Shape s;

        s = new Circle();
        s.draw();

        s = new Square();
        s.draw();

        s = new Shape();
        s.draw();
    }
}

class Shape {
    void draw() {
        System.out.println("Drawing a shape");
    }
}

class Circle extends Shape {
    @Override
    void draw() {
        System.out.println("Drawing a circle");
    }
}

class Square extends Shape {
    @Override
    void draw() {
        System.out.println("Drawing a square");
    }
}
```

Expected output:

```text
Drawing a circle
Drawing a square
Drawing a shape
```

`s` is always a `Shape` variable. But it points to a `Circle`, then a `Square`, then a plain `Shape`. Each call runs the version that belongs to the object at that moment.

### What Is Not Dispatched

Dynamic dispatch only applies to overridden **instance methods**.

**Fields** and **static methods** are chosen by the **reference type**, at compile time.

```java runnable title="Fields follow the variable type, methods follow the object"
public class Main {
    public static void main(String[] args) {
        Parent p = new Child();

        System.out.println("Field: " + p.name);
        System.out.println("Method: " + p.getName());
    }
}

class Parent {
    String name = "Parent field";

    String getName() {
        return "Parent method";
    }
}

class Child extends Parent {
    String name = "Child field";

    @Override
    String getName() {
        return "Child method";
    }
}
```

Expected output:

```text
Field: Parent field
Method: Child method
```

The field was chosen by the variable type, `Parent`. The method was chosen by the object type, `Child`.

This is one more reason to keep fields `private` and reach data through methods.

## Upcasting and Downcasting

**Casting** an object means treating it as a different type in the same family.

### Upcasting

**Upcasting** means storing a child object in a **parent-type** variable.

```text
Animal a = new Dog();   // upcasting
```

It is always safe, because every `Dog` **is an** `Animal`. Java does it automatically, with no cast written.

After upcasting, you can only call methods the **parent** type knows about. The `Dog` object still has its own methods, but the `Animal` variable cannot see them.

### Downcasting

**Downcasting** means turning a parent-type variable back into a child type, so you can use the child's extra methods.

```text
Dog d = (Dog) a;   // downcasting
```

You must write the cast yourself. It is **not always safe**. If `a` actually points to a `Cat`, the cast fails while the program runs, with a `ClassCastException`.

### Checking With instanceof

Before downcasting, check the real type with **`instanceof`**. It returns `true` if the object is that type (or a child of it).

Modern Java lets you check and cast in one step, with a **pattern**:

```text
if (a instanceof Dog d) {
    d.fetch();   // d is already a Dog here
}
```

```java runnable title="Upcast freely, downcast only after checking"
public class Main {
    public static void main(String[] args) {
        Animal[] pets = { new Dog(), new Cat(), new Dog() };

        for (Animal a : pets) {
            a.eat();

            if (a instanceof Dog d) {
                d.fetch();
            } else if (a instanceof Cat c) {
                c.climb();
            }
        }
    }
}

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

class Dog extends Animal {
    void fetch() {
        System.out.println("  Dog fetches the ball");
    }
}

class Cat extends Animal {
    void climb() {
        System.out.println("  Cat climbs the tree");
    }
}
```

Expected output:

```text
Eating...
  Dog fetches the ball
Eating...
  Cat climbs the tree
Eating...
  Dog fetches the ball
```

Storing the pets in an `Animal[]` is upcasting. Every pet can `eat()`.

`fetch()` only exists on `Dog`. So the loop checks `a instanceof Dog d` before using it. For the cat, that check is false, so no unsafe cast happens.

| | Upcasting | Downcasting |
|---|---|---|
| Direction | Child → parent | Parent → child |
| Example | `Animal a = new Dog();` | `Dog d = (Dog) a;` |
| Cast written? | No, automatic | Yes |
| Always safe? | Yes | No, check with `instanceof` first |
| Methods you can call | Only the parent's | The child's too |

A wrong downcast compiles fine, then stops the program with `ClassCastException: class Cat cannot be cast to class Dog`. Always check with `instanceof` before you downcast.

`static` methods are not overridden. If a child declares a static method with the same signature, it **hides** the parent's one instead. The version called is chosen by the variable's type, not the object's.

## Compile-Time vs Runtime Polymorphism

| | Compile-time polymorphism | Runtime polymorphism |
|---|---|---|
| Achieved by | Method overloading | Method overriding |
| Decided by | The compiler | The JVM, while the program runs |
| Based on | The arguments' types and count | The actual object's type |
| Needs inheritance? | No | Yes |
| Methods involved | Several methods in one class | The same method in parent and child |
| Speed | Slightly faster, no run-time lookup | Tiny run-time lookup |
| Flexibility | Lower | Higher, new types work with old code |
| Example | `print(int)` and `print(String)` | `Dog.speak()` overriding `Animal.speak()` |

In simple words: overloading picks by **what you pass**. Overriding picks by **what you call it on**.

## Common mistakes

The following mistakes are common when overriding methods and using polymorphism. Each row shows what you see, why it happens and how to fix it.

| Symptom | Cause | Fix |
|---|---|---|
| The parent's method runs instead of the child's | The child changed the name or parameters | Match the signature, and add `@Override` |
| `method does not override or implement a method` | `@Override` found no matching parent method | Fix the name or parameters |
| `weaker access privileges` | The overriding method is less visible | Use the same or wider access |
| `cannot find symbol` on a child method | The variable's type is the parent | Check with `instanceof`, then use the child type |
| `ClassCastException` | Downcast to a type the object is not | Check with `instanceof` first |
| `overridden method is final` | The parent method is `final` | Do not override it, or remove `final` |

## What to Remember

- Polymorphism means one name with many forms.
- Overloading gives compile-time polymorphism: same name, different parameters, chosen by the compiler.
- Overriding gives runtime polymorphism: a child replaces a parent method, and the real object decides which version runs.
- Always write `@Override`. It catches typos in names and parameters.
- Dynamic method dispatch picks methods by the object's type. Fields and static methods follow the variable's type.
- Upcasting is automatic and safe. Downcasting needs a cast, so check with `instanceof` first.

Next: Abstraction
