Method overriding and super
Replacing inherited behavior, calling back up to the parent, and the @Override safety net.
Finished reading?
Mark this session so you can track where you are.
Replacing inherited behavior, calling back up to the parent, and the @Override safety net.
Finished reading?
Mark this session so you can track where you are.
Last session a subclass got to keep everything its parent had, for free. That is a fine deal when the inherited behavior is exactly what you want. But often it is almost right and not quite. A Dog is an Animal, yes, but it does not make a generic animal noise. It barks. This session is about a subclass quietly saying: thank you for the method, I will take it from here.
super.method() to call the parent's version from inside your own override.@Override does and why writing it is a good habit.extends inherits the fields and methods of its superclass. You also need methods (parameters, return values, and the idea of a method signature) and object references (a variable holds a reference to an object, not the object itself). We will not use interfaces, generics, or collections.Here is where we left inheritance. An Animal can speak, and a Dog inherits that ability without writing a single new line. Watch what a dog actually says.
public class Main { public static void main(String[] args) { Animal a = new Animal(); Dog d = new Dog(); System.out.println(a.speak()); System.out.println(d.speak()); }} class Animal { String speak() { return "Some generic animal sound"; }} class Dog extends Animal { // Dog adds nothing. It just inherits speak() from Animal.}Some generic animal sound Some generic animal sound
Both lines print the same thing. The dog inherited speakword for word, so it makes the generic noise. That is correct inheritance and it is also useless: nobody believes a dog says “Some generic animal sound”. We need the dog to keep being an Animal in every other way, but to answer speak differently.
Inheritance gives you the parent's behavior for free. Overriding lets you swap out one piece of that behavior while keeping the rest.
To change what a dog says, we write a method in Dog with the same signature as the one in Animal. Same name, same parameters, same return type. When a subclass redefines an inherited method like this, we say it overridesit. The dog's version wins for dogs; the animal's version still stands for plain animals.
public class Main { public static void main(String[] args) { Animal a = new Animal(); Dog d = new Dog(); System.out.println(a.speak()); System.out.println(d.speak()); }} class Animal { String speak() { return "Some generic animal sound"; }} class Dog extends Animal { String speak() { return "Woof"; }}Some generic animal sound Woof
Now the two lines differ. a.speak() runs the Animal version and prints the generic sound. d.speak() runs the Dog version and prints Woof. The Dog still inherits everything else from Animal; we only replaced the one method we cared about. The parent's speakis not deleted or damaged. It is simply hidden behind the dog's own version, for dogs.
Dog.speak() stands in place of Animal.speak(). They share a signature, so for a dog there is only ever one speak()you can call, and it is the dog's. A reader of your code does not have to wonder which one applies; the most specific version always does.Sometimes you do not want to throw the parent's work away. You want to add to it. A cat makes its own sound, but suppose we still want the generic animal sound tucked inside the cat's answer, so we can see that a cat is, underneath, still an animal. Java gives you a keyword for reaching up to the version you just overrode: super.
Inside an overriding method, super.speak() means “run the speakthat my parent defined, not my own”. Without super, writing speak()here would call the cat's own version and loop forever. The super prefix is how you say: skip my override, go one level up.
public class Main { public static void main(String[] args) { Cat c = new Cat(); System.out.println(c.speak()); }} class Animal { String speak() { return "generic sound"; }} class Cat extends Animal { String speak() { return "Meow (" + super.speak() + ")"; }}Meow (generic sound)
Trace it by hand. c.speak() enters the Cat version. It builds a string starting with "Meow (", then hits super.speak(), which jumps up to Animal.speak() and gets back "generic sound". The pieces join into Meow (generic sound), and that is what prints. The cat extended the parent's behavior instead of replacing it outright.
Override to replace. Override plus
superto extend. Replacing throws the old behavior away; extending wraps your own work around the parent's.
super(...) with parentheses right after it: that calls the parent constructor. Here super.speak() with a dot calls a parent method. Same keyword, two uses: super(...) for the constructor, super.something() for a method.Here is the full cast in one program. Dog replaces speak outright. Cat extends it with super. A plain Animal keeps the original. Press Run and step through. Watch each speak() call land in a different method body even though all three calls look identical.
public class Main { public static void main(String[] args) { Animal generic = new Animal(); Dog rex = new Dog(); Cat lily = new Cat(); System.out.println(generic.speak()); System.out.println(rex.speak()); System.out.println(lily.speak()); }} class Animal { String speak() { return "Some animal sound"; }} class Dog extends Animal { String speak() { return "Woof!"; }} class Cat extends Animal { String speak() { return "Meow (" + super.speak() + ")"; }}Some animal sound Woof! Meow (Some animal sound)
The output is three different lines: Some animal sound, then Woof!, then Meow (Some animal sound). Three objects, one method name, three behaviors. The object decides which body runs, and it decides based on what it actually is.
Overriding has one quiet danger: it depends on matching the signature exactly, and a small slip means you do not override at all. Suppose you meant to override speak() but you typed speak(String mood) by mistake, or you misspelled the name as spaek. Java will not complain. It will happily treat your method as a new, separate method, and the parent's speak() will keep running for your subclass. You get the old behavior and no error, which is the worst kind of bug: silent.
The fix is a one-word note to the compiler called an annotation. Writing @Override on the line above a method tells Java: I believe this method overrides one from a parent. If it does not, please stop and tell me. Now a typo or a wrong parameter list becomes a loud compile error instead of a silent surprise.
Here is the same Cat with the annotation in place. This is exactly how you should write overrides from now on.
public class Main { public static void main(String[] args) { Cat c = new Cat(); System.out.println(c.speak()); }} class Animal { String speak() { return "generic sound"; }} class Cat extends Animal { @Override String speak() { return "Meow (" + super.speak() + ")"; }}Meow (generic sound)
@Override. The code above is correct, standard Java and would print exactly Meow (generic sound) in a real compiler. When you practise in the playground, drop the @Override line; when you write real Java in an editor, always keep it.Two words that sound alike and get confused constantly. They are different ideas that happen to both involve methods sharing a name. Hold them apart with one question: are we talking about one class, or a parent and a child?
print(int) and print(String) are two overloads. Java picks which one to call by looking at the argument types you pass, and it decides while compiling, before the program runs.Dog.speak()print(int) vs print(String)extends).Here is overloading on its own, no inheritance in sight. One class, three methods named show, each taking a different parameter type. Java reads each call and matches it to the right overload by the type of the argument.
public class Main { public static void main(String[] args) { Printer p = new Printer(); p.show(42); p.show("hello"); p.show(3.5); }} class Printer { void show(int x) { System.out.println("int: " + x); } void show(String x) { System.out.println("String: " + x); } void show(double x) { System.out.println("double: " + x); }}int: 42 String: hello double: 3.5
show calls to the first one. The output shown above is what a real Java compiler produces, because real Java chooses the overload by argument type. Trust the read-along here; the overriding examples in this session do run correctly.We have been saying “the object decides”. Let us make that precise, because it is the engine under everything here. When you call thing.speak(), Java does not look at the declared type of thing. It looks at the actual object thingcurrently points to, finds that object's real class, and runs that class's version of speak. This runtime choice has a name: dynamic dispatch.
The sharpest way to feel this is to point a parent-typed variable at a child object. Put a row of animals in a single array whose declared type is Animal, then call speak() on each in one ordinary loop. The loop body never mentions Dog or Cat. Yet each call lands in the right method.
public class Main { public static void main(String[] args) { Animal[] zoo = new Animal[3]; Animal thing = new Animal(); thing.name = "Thing"; Dog rex = new Dog(); rex.name = "Rex"; Cat lily = new Cat(); lily.name = "Lily"; zoo[0] = thing; zoo[1] = rex; zoo[2] = lily; for (int i = 0; i < zoo.length; i = i + 1) { System.out.println(zoo[i].name + " says " + zoo[i].speak()); } }} class Animal { String name; String speak() { return "..."; }} class Dog extends Animal { String speak() { return "Woof"; }} class Cat extends Animal { String speak() { return "Meow (" + super.speak() + ")"; }}Thing says ... Rex says Woof Lily says Meow (...)
Every slot of zoo is declared as an Animal, so the loop can treat them uniformly. But when zoo[i].speak() runs, Java checks the real object in that slot. Slot 0 holds a plain animal, so it prints Thing says .... Slot 1 holds a dog, so it prints Rex says Woof. Slot 2 holds a cat, so it prints Lily says Meow (...). One loop, one method name, three behaviors, chosen object by object as the program runs.
Animal variable at a Dog without comment, and put dogs and cats into an Animal[]. The rules that make this safe, called upcasting, and what happens when you need to go back the other way, are part of polymorphism. You do not need them to understand overriding; just notice that a parent-typed reference can hold a child object.Try each one yourself first, then open the answer.
Cat class has String speak() returning "Meow (" + super.speak() + ")", and Animal.speak() returns "generic sound". What does new Cat().speak() return, and what would go wrong if we wrote plain speak() instead of super.speak()?String speak(String mood) in Dog, hoping to override Animal's String speak(). Did they override it? What single line would have caught the mistake?add in one class, one taking two ints, one taking two doubles. (b) SavingsAccount redefines withdraw() from its parent Account with the same signature.Animal[]: a plain Animal named Thing, a Dog named Rex, a Cat named Lily. Why does zoo[i].speak() print three different sounds even though every slot is declared as Animal?Take these away. They continue exactly what we just did.
Animal with String speak() returning "...", then add three subclasses: Dog returning "Woof", Cow returning "Moo", and Cat returning "Meow (" + super.speak() + ")". Make one of each in main and print all four sounds. Confirm the cat's line shows the parent's sound inside the parentheses. Leave out @Override in the playground; the runner does not process it.Vehicle class with a method String describe() that returns "A vehicle". Make a Car subclass whose describe() returns the parent's text plus extra detail, using super.describe(), so a car describes itself as A vehicle with 4 wheels. Print a Car's description. The point is to extend, not replace.@Override line is worth typing even though the program runs the same with or without it. Give a concrete example of a typo it would catch.