Lesson 17Phase 2: Objects
Method overriding and super
Replacing inherited behavior, calling back up to the parent, and the @Override safety net.
By BasithUpdated 55 min
- overriding
- super
- @Override
- dynamic dispatch
- overriding vs overloading
In inheritancea subclass got its parent's fields and methods 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 lesson is about a subclass saying: thank you for the method, I will take it from here.
- Replace an inherited method by redefining it in the subclass, and say what overriding means.
- Use
super.method()to run the parent's version from inside your own override. - Explain what
@Overridedoes and why it belongs on every override. - Follow the rules for an override, including why
toStringmust bepublic. - Tell overriding apart from overloading without hesitating.
- Predict which version of a method runs when a parent-typed variable points at a child object.
extends inherits the fields and methods of its superclass, and builds its parent part with super(...). You also need methods (parameters, return values and overloading) and object references (a variable holds a reference to an object, not the object itself).The inherited method is almost right#
Here is where inheritance left off. 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.}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”. The dog should stay an Animal in every other way and answer speak differently.
Key ideaInheritance gives you the parent's behavior for free. Overriding lets you swap out one piece of that behavior while keeping the rest.
Overriding: redefine the method in the subclass#
To change what a dog says, write a method in Dog with the same name, the same parameters and the same return type as the one in Animal. When a subclass redefines an inherited method like this, it overridesthat method. The dog's version wins for dogs, and 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"; }}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; only the one method was replaced. The parent's speak is not deleted or damaged, and plain animals still use it.
Dog.speak() takes the place of Animal.speak() for every Dog object. A call such as d.speak()never runs both, and you never have to choose between them. Java runs the version in the object's own class when that class has one, and the inherited version when it does not.Animal has the method String speak(). Which method, written inside Dog, overrides it?Calling back to the parent with super#
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. Java has a keyword for reaching the version you just overrode: super.
Inside an overriding method, super.speak() means “run the speakthat my parent class has, not my own”. Without super, writing speak()there would call the cat's own version again, which would call itself again, and so on until Java gives up with a StackOverflowError. The superprefix skips your own version and goes to the parent's.
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() + ")"; }}Trace it by hand. c.speak() enters the Cat version. It builds a string starting with "Meow (", then reaches 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.
Key ideaOverride to replace. Override and call
superto extend. Replacing throws the old behavior away; extending wraps your own work around the parent's.
Here is that receipt, scrambled. Order matters inside the override: the parent's lines come first, then the extra one.
PremiumReceipt prints the normal receipt first and then Thank you for being a member!. Each method belongs inside its own class.Tap a line, then tap where it should go. Or use the arrows.
super(...)with parentheses right after it, which calls the parent's constructor. Here super.speak(), with a dot, calls a parent's method. Same keyword, two uses: super(...) for the constructor, super.something() for a method.Putting it together: Animal, Dog, Cat#
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 alike.
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() + ")"; }}The output is three different lines: Some animal sound, then Woof!, then Meow (Some animal sound). Three objects share one method name and behave three different ways. Each object decides which body runs, based on what it actually is.
Puppy extends Dog, and Dog extends Animal. Animal's speak() returns "Some animal sound", Dog's returns "Woof", and Puppy's returns super.speak() + "!". What does new Puppy().speak() return?The @Override safety net#
Overriding depends on matching the name and parameters exactly, and a small slip means you do not override at all. Here is a Dog whose author meant to override speak() but swapped two letters in the name. Decide what it prints before you run it.
public class Main { public static void main(String[] args) { Dog d = new Dog(); System.out.println(d.speak()); }} class Animal { String speak() { return "Some generic animal sound"; }} class Dog extends Animal { String spaek() { return "Woof"; }}The dog's method is spelled spaek. What does d.speak() print?
It prints Some generic animal sound. Java treats spaek as a brand new method with its own name, so a Dog now has two methods: the speak() it inherited and an unrelated spaek(). The call d.speak() finds the inherited one. There is no error and no warning, just the wrong sound. A wrong parameter list or a capital letter in the wrong place fails in the same silent way.
The fix is a one-word note to the compiler called an annotation. Writing @Override on the line above a method tells Java: this method is meant to override one from a parent class, so please check. If it does not override anything, the program does not compile, and the silent bug turns into an error you can fix in seconds. Here is the same dog with the annotation added, and what javac says about it:
public class Main { public static void main(String[] args) { Dog d = new Dog(); System.out.println(d.speak()); }} class Animal { String speak() { return "Some generic animal sound"; }} class Dog extends Animal { @Override String spaek() { return "Woof"; }}Main.java:15: error: method does not override or implement a method from a supertype
@Override
^
1 errorThe compiler points straight at the annotation. (From Java 26 on, the message also names the method: spaek() in Dog does not override or implement a method from a supertype.) Fix the spelling to speak and the program compiles and barks.
Here is the cat from earlier with the annotation in place. Write every override this way 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() + ")"; }}The rules an override must follow#
Matching the name and the parameters is the main rule. The compiler checks two more, and both come up early in real code.
- The return type must match the parent's. A
void speak()cannot override aString speak(). An override may also return a more specific type, such as aDogwhere the parent returns anAnimal. That is called a covariant return type, and you will rarely need it at first. - An override cannot be less visible than the method it replaces. If the parent's method is
public, the override must bepublictoo. Aprotectedmethod may becomepublicin a subclass, but neverprivate.
toString: a method every class inherits#
The second rule bites the first time you override a method you did not write yourself. Every class extends Object, as you saw in inheritance, and Object gives every object a toString() method that turns it into text. Whenever you print an object, println calls its toString(). Object's version gives the class name, an @ and a number, something like Book@1b6d3586, and the number usually changes from one run to the next. Override toString and your objects print something useful instead.
public class Main { public static void main(String[] args) { Book b = new Book("Dune", "Frank Herbert"); System.out.println(b); System.out.println("On the shelf: " + b); }} class Book { private String title; private String author; Book(String title, String author) { this.title = title; this.author = author; } @Override public String toString() { return title + " by " + author; }}Step through it. Each println calls your toString(), and so does joining a book to a string with +. The two lines are Dune by Frank Herbert and On the shelf: Dune by Frank Herbert.
Notice the public in front. Object declares toString as public, so an override has to be public as well. A method with no access word at all, like the speak() methods on this page, is less visible than that, and javac rejects it:
public class Main { public static void main(String[] args) { Book b = new Book("Dune", "Frank Herbert"); System.out.println(b); System.out.println("On the shelf: " + b); }} class Book { private String title; private String author; Book(String title, String author) { this.title = title; this.author = author; } @Override String toString() { return title + " by " + author; }}Main.java:19: error: toString() in Book cannot override toString() in Object
String toString() {
^
attempting to assign weaker access privileges; was public
1 error“Weaker access privileges” means “less visible”. The fix is the one word public.
Player shows something like Player@1b6d3586. Override toString so that a player prints as Ada has 91 points. Remember the annotation and the access word.Overriding versus overloading#
Overriding and overloading sound alike, and people mix them up all the time. Both involve methods that share a name, but they are different ideas. One question keeps them apart: do the parameter lists match?
- Overloading is several methods with the same name but different parameter lists. You met it in methods:
area(int side)andarea(int width, int height)are two overloads. Java picks one by looking at the arguments you pass, and it decides while compiling, before the program runs. - Overriding is one method, with the same name and parameters, redefined in a subclass. There is a parent version and a child version. Java picks one by looking at the object's actual class, and it decides while the program runs.
extends).Dog.speak() replacing Animal.speak()show(int) next to show(String)Here is overloading on its own, with no inheritance in sight. One class has three methods named show, each taking a different parameter type. Step through it and watch which show each call jumps into.
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); }}It prints int: 42, String: hello and double: 3.5. Java matched each call to an overload by the type of its argument: 42 is an int, "hello" is a String and 3.5 is a double.
SavingsAccount extends Account, and Account has void deposit(double amount). Which of these is overriding?Who decides which version runs?#
This page keeps saying “the object decides”. Here is exactly what that means. When you call thing.speak(), the compiler checks the declared type of thing to make sure it has a speak() at all. Which version runs is settled later, while the program runs: Java looks at the object thingpoints to at that moment, finds that object's class, and runs that class's version of speak. This choice at run time is called dynamic dispatch.
The clearest way to see it is to point a parent-typed variable at a child object. Put a row of animals in one 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 { @Override String speak() { return "Woof"; }} class Cat extends Animal { @Override String speak() { return "Meow (" + super.speak() + ")"; }}Every slot of zoo is declared as an Animal, so the loop can treat them all the same way. 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 (...). The loop has one method call, and Java chooses its version object by object as the program runs.
Dynamic dispatch also applies when the call is written inside the parent's own code. Below, Animal has a describe() method that calls speak(), and Dog overrides only speak(). Decide what the two lines print.
public class Main { public static void main(String[] args) { Animal generic = new Animal("Thing"); Dog rex = new Dog("Rex"); System.out.println(generic.describe()); System.out.println(rex.describe()); }} class Animal { String name; Animal(String name) { this.name = name; } String speak() { return "..."; } String describe() { return name + " says " + speak(); }} class Dog extends Animal { Dog(String name) { super(name); } @Override String speak() { return "Woof"; }}Which speak() does describe() call when it runs for Rex?
The second line is Rex says Woof. describe() lives in Animal and Dog never touched it, yet the speak() inside it ran the dog's version. A bare call like speak() inside a method means this.speak(), a call on the object the method is running for, and here that object is a Dog. Wherever the call is written, the object decides. Open it in the playground and step through: the second describe() jumps from Animal's code into Dog's speak().
Key ideaAn ordinary call to an overridden method runs the object's own version, wherever the call is written and however the variable is declared.
TeenAccount works like an Account, except that it refuses to hand out more than 100 at a time. Override withdraw in TeenAccount: for an amount over 100, print Limit is 100 and change nothing; for any other amount, let Account's withdraw do the work.Overloads look at the variable, overrides look at the object#
The two kinds of choice can meet in one program. Below, Keeper has two overloads of feed: one for any Animal and one for a Dog. The variable a holds a dog, but its declared type is Animal. Guess which feed the call lands in, then step through and watch.
public class Main { public static void main(String[] args) { Keeper k = new Keeper(); Animal a = new Dog(); k.feed(a); a.speak(); }} class Keeper { void feed(Animal x) { System.out.println("feed(Animal) runs"); } void feed(Dog x) { System.out.println("feed(Dog) runs"); }} class Animal { void speak() { System.out.println("Some animal sound"); }} class Dog extends Animal { @Override void speak() { System.out.println("Woof"); }}It prints feed(Animal) runs, then Woof. The overload is picked while compiling, and all the compiler knows about a is its declared type, Animal, so feed(Animal) runs even though the object is a dog. The override is picked while the program runs, from the real object, so speak() barks.
The same rule holds for classes you did not write. Object is the parent of every class, so an Object variable can hold any object, a String included. Below, word and thing point at the very same string.
public class Main { public static void main(String[] args) { Printer p = new Printer(); String word = "hello"; Object thing = word; p.describe(word); p.describe(thing); }} class Printer { void describe(Object x) { System.out.println("Object: " + x); } void describe(String x) { System.out.println("String: " + x); }}Both calls pass the same "hello" object. Which describe runs each time?
The first call prints String: hello and the second prints Object: hello. The object is the same both times. Only the declared type of the variable changed, and that alone decided which overload ran.
Animal variables at dogs and cats without comment. Why that is always allowed (it is called upcasting), and how to get from an Animal back to a Dog when you need to, are part of polymorphism. For overriding, it is enough to know that a parent-typed variable can hold a child object.Try it
Answer each one in your head or on paper 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?Practice
Work through these on your own. Each one builds on this page.
Animal with String speak() returning "...", then add three subclasses: Dog returning "Woof", Cow returning "Moo", and Cat returning "Meow (" + super.speak() + ")". Mark each override with @Override. Make one of each in main and print all four sounds. Confirm the cat's line shows the parent's sound inside the parentheses.Vehicle has String describe() returning "A vehicle". Car extends Vehicle and returns the parent's text plus " with 4 wheels". ElectricCar extends Car and returns the parent's text plus ", battery powered". Use super.describe() in both overrides, print an ElectricCar's description, and confirm it reads A vehicle with 4 wheels, battery powered. Then explain which describe() each super reached.@Override line is worth typing even though a correct program runs the same with or without it. Give a concrete example of a mistake it would catch.Further reading#
- Overriding and Hiding Methods, the dev.java tutorial on the rules for an override, including return types and access
- Polymorphism, the dev.java tutorial, with its section on using the keyword super
- The Override annotation in the Java SE 25 API documentation
- Inheritance, Overriding, and Hiding, section 8.4.8 of the Java Language Specification
Short guides on this topic
Quick answers to what people search for about it, with code to run.
Spotted a mistake, or a part that did not make sense? Report it by email