Skip to content
Theory · Inheritance and polymorphism · lesson 3 of 10

Overriding and polymorphism

Now picture a universal remote labelled "Animal". It has one button: "Voice". Point the remote at a dog and press it: the dog barks. Point it at a cat, and the cat meows. The button is the same; what it does depends on whoever the remote is pointed at.

In Java the remote is a variable, and whoever it is pointed at is an object. But first the dog has to learn its own voice. Animal has a method sound, and the dog inherits it, but a dog should bark:

JAVA
public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal();
        animal.sound(); // ...
        Dog dog = new Dog();
        dog.sound();    // Woof
    }
}

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

Let's walk through the program step by step:

  1. In Animal the method sound prints ...: nobody knows what an animal in general sounds like.
  2. Dog declares a method with the same name and the same parameter list, which is empty here. We'll look at the @Override mark in front of it below.
  3. animal.sound() on an Animal object prints ....
  4. dog.sound() on a Dog object prints Woof: the version in Dog replaced the parent's version.

Overriding a method

Overriding is declaring a method in a subclass with the same signature as in the parent: the same name and the same parameter types. The new version replaces the inherited one for objects of the subclass. Objects of Animal itself keep the old one.

The @Override mark

The @Override mark is a note for the compiler: "this method overrides a method of the parent". The compiler checks it. Say you made a typo and wrote Sound instead of sound. Without the mark you get a new method, and the dog still prints .... With the mark the compiler answers at once:

TEXT
error: method does not override or implement a method from a supertype

The program works without @Override too, but then nobody notices the typo. That is why the mark goes in front of every override.

toString is overridden too

You have already overridden a method: the toString you wrote for a point. The method toString is declared in Object, and Point replaced it with its own version, so it gets @Override too. And public was required there because an override cannot narrow access: Object declares toString as public.

Variable type and object type

The interesting part starts when a variable of type Animal leads to a dog. That is allowed: a dog is an animal, so a reference to a Dog can go into an Animal variable. What does the call print?

JAVA
Animal a = new Dog();
a.sound(); // Woof

It prints Woof. The variable is declared as Animal, but it leads to a dog, and the object picks the version of the method. This is polymorphism: the same call a.sound() behaves differently depending on which object a leads to.

Back to the remote. The variable's type is the remote, and the object is the device it is pointed at. It works like this:

  • the variable's type decides which methods can be called, and the compiler checks it before the program runs;
  • the object decides which version of a method runs, and the JVM picks it while the program runs.

In the widget, the variable a first leads to a dog and then to a new Animal object. Press "Next step" and watch the frames column: which sound method shows up in it.

LiveWhose sound method runsstep 1 of 7 · frames: 1

new Dog() builds a dog, and the variable a of type Animal gets a reference to it.

Watch which object a leads to at the moment of the call

The call a.sound() reads the same, yet different methods run. The compiler does not know which object a will lead to at the moment of the call, so the choice is put off until the program runs. Picking the method this way, while the program runs, is called dynamic dispatch.

Interviewers like to ask this: what does Animal a = new Dog(); a.sound() print? The answer: Woof, because an overridden method is picked by the object, not by the variable's type.

Here the remote picture is off. A real remote can send a command the device does not understand, and nothing happens. Java never does that: a subclass has every method of its parent, so every button of the Animal remote works on a dog.

The parent's version: super.sound()

Sometimes a subclass needs an addition rather than a replacement: do what the parent does, plus something of its own. Then the new version calls the old one through super:

JAVA
public class Main {
    public static void main(String[] args) {
        Animal a = new Dog();
        a.sound();
    }
}

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

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

The class Animal is the same here, only written on one line. Let's walk through the call a.sound():

  1. The object is a dog, so the Dog version runs.
  2. super.sound() calls the parent's version, and it prints ....
  3. Then the Dog version's own line runs and prints Woof.

Without super.sound() the parent's version would not run at all: overriding replaces a method, it does not add to it. And do not confuse this call with super(…) in a constructor: there super calls the parent's constructor, here it calls the parent's method.

Common mistake

A method that only the subclass has was called through a variable of the parent's type. Say Dog has its own method bark:

JAVA
Animal pet = new Dog();
pet.bark(); // ✗
TEXT
error: cannot find symbol

The program does not compile, even though the object is a dog. The compiler checks the call against the variable's type, and Animal has no bark method. The "Animal" remote has no "Bark" button, whoever you point it at. If you need the dog's own methods, keep the reference in a variable of type Dog: Dog d = new Dog(); d.bark();.

Key points

  • Overriding is a subclass method with the same signature as the parent's; the @Override mark asks the compiler to check that.
  • In Animal a = new Dog(); a.sound() the Dog version runs: an overridden method is picked by the object while the program runs.
  • The variable's type decides which methods can be called: a.bark() does not compile even when a leads to a dog.
  • super.sound() calls the parent's version from an overriding method.