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:
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:
- In
Animalthe methodsoundprints...: nobody knows what an animal in general sounds like. Dogdeclares a method with the same name and the same parameter list, which is empty here. We'll look at the@Overridemark in front of it below.animal.sound()on anAnimalobject prints....dog.sound()on aDogobject printsWoof: the version inDogreplaced 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:
error: method does not override or implement a method from a supertypeThe 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?
Animal a = new Dog();
a.sound(); // WoofIt 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.
1public class Main {2 public static void main(String[] args) {3 Animal a = new Dog();4 a.sound();5 a = new Animal();6 a.sound();7 }8}9 10class Animal {11 void sound() {12 System.out.println("...");13 }14}15 16class Dog extends Animal {17 @Override18 void sound() {19 System.out.println("Woof");20 }21}
new Dog() builds a dog, and the variable a of type Animal gets a reference to it.
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:
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():
- The object is a dog, so the
Dogversion runs. super.sound()calls the parent's version, and it prints....- Then the
Dogversion's own line runs and printsWoof.
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:
Animal pet = new Dog();
pet.bark(); // ✗error: cannot find symbolThe 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
@Overridemark asks the compiler to check that. - In
Animal a = new Dog(); a.sound()theDogversion 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 whenaleads to a dog. super.sound()calls the parent's version from an overriding method.