Overloading or overriding
Overloading and overriding sound alike, and interviewers ask about the difference all the time. Both decide which of several methods with one name runs. The difference is who decides and when.
Back to the remote. It may have several similar buttons, and the right one is picked by the labels on the remote itself, before anything is pressed. That is overloading. What happens after the press is decided by the device the remote is pointed at. That is overriding.
You already know overloading: the compiler picks a version by the types of the arguments. Let's see what happens when the argument is a variable of the parent's type that leads to a subclass object:
public class Main {
static void greet(Animal a) { System.out.println("animal"); }
static void greet(Dog d) { System.out.println("dog"); }
public static void main(String[] args) {
Dog d = new Dog();
Animal a = d;
greet(d); // dog
greet(a); // animal
}
}
class Animal {}
class Dog extends Animal {}Let's walk through the program step by step:
- The method
greetis overloaded: one version takes anAnimal, the other aDog. Animal a = d;puts intoaa reference to the same dog thatdleads to.dhas the typeDog, andgreet(d)has an exact match: the version forDog. It printsdog.- The object in
ais the same, but the variable's type isAnimal. Forgreet(a)the compiler picks the version forAnimal, and it printsanimal.
The compiler picks the overload
The compiler picks an overload while it reads the program's text, long before the program runs. There are no objects at that point: there are only declared types. All the compiler knows about a is that it is a variable of type Animal.
It could not work any other way. The reference in a may be set inside an if, and which object ends up there becomes clear only when the program runs. So the compiler looks for a version by the declared types of the arguments:
- an exact match first:
greet(d)with a variable of typeDogcallsgreet(Dog d); - if there is none, a version that fits: a dog is an animal, so a
Dogwould also fit a parameter of typeAnimal.
For the same reason, any object fits a variable or parameter of type Object, even a string: every class is a subclass of Object.
The object picks the override
Now let's give the classes a method sound and use both mechanisms through one variable:
public class Main {
static void greet(Animal a) { System.out.println("animal"); }
static void greet(Dog d) { System.out.println("dog"); }
public static void main(String[] args) {
Animal a = new Dog();
greet(a); // animal
a.sound(); // Woof
}
}
class Animal { void sound() { System.out.println("..."); } }
class Dog extends Animal {
@Override void sound() { System.out.println("Woof"); }
}Let's take the two calls apart:
greet(a)printsanimal: the compiler picked the overload by the declared typeAnimal.a.sound()printsWoof: the JVM picked the overriding version by the object, and the object is a dog.
The same variable a gave two different answers in one program. That is the main difference between the two mechanisms.
Side by side
| Overloading | Overriding | |
|---|---|---|
| Where | in one class or in a subclass | only in a subclass |
| Parameters | different | the same |
| Who picks | the compiler | the JVM |
| When | before the program runs | while the program runs |
| By what | the declared types of the arguments | the object |
In an interview the answer fits in one sentence. The compiler picks an overload by the declared types of the arguments, and the JVM picks an override by the object while the program runs.
Here the remote picture is off. On a real remote you pick the button yourself, and you can see the device. The compiler cannot see the device: it reads only the labels, that is, the declared types.
Common mistake
You meant to override the parent's method but changed its parameters:
class Dog extends Animal {
@Override void sound(String mood) { // ✗
System.out.println("Woof " + mood);
}
}error: method does not override or implement a method from a supertypeThe program does not compile. sound(String mood) and sound() have different signatures, so the new method does not override the old one; it overloads it. Without @Override the code would compile, and a.sound() on a dog would print .... To override a method, repeat its parameters exactly: void sound().
Key points
- Overloading is methods with one name and different parameters; the compiler picks the version before the program runs, by the declared types of the arguments.
- Overriding is a subclass method with the same signature; the JVM picks the version while the program runs, by the object.
- With
Animal a = new Dog(),greet(a)calls the version forAnimal, anda.sound()theDogversion. - A different parameter list in a subclass gives overloading, not overriding, and
@Overridecatches it.