Overriding a method, such as toString
Your class inherits a method it does not like. Overriding is writing your own version, with the same header, in your own class, so that from then on your objects use yours. The parent's version is still there and is still what everybody else uses. Yours is what runs when the object in front of Java is one of yours.
Start with the one everybody overrides
Every class in Java extends Object, so every class already has a toString. Print a student without doing anything about it and you get this:
Student@1a2b3c
That is the class name, an @, and a number Java computed for the object (its hash code, in hexadecimal). It tells you nothing about the student's data. Write your own and the same println tells you something:
@Override
public String toString()
{
return "Student{name=" + this.name + ", gpa=" + this.gpa + "}";
}
Student{name=Ada, gpa=3.9}
You never call toString in that example, and it runs anyway. Java calls it for you any time it needs text from an object: inside System.out.println(student), and on either side of a + when the other side is a String. That is why a broken toString shows up in output you did not think went near it.
What makes it an override and not a new method
Java decides that by the header, and it is strict about it. To replace the parent's method, yours needs the same name, the same parameter list in the same order, the same return type or a subclass of it, and access that is not narrower than the parent's. Change the parameter list at all and you have not replaced anything. You have added a second, unrelated method that happens to share a name, and the parent's version is still sitting there, still the one that gets called.
That failure is silent. The classic case is equals:
// looks careful, replaces nothing
public boolean equals(Student other)
{
return this.name.equals(other.name);
}
Every object inherits equals(Object obj), with an Object parameter. A method taking a Student has a different parameter list, so it does not override; it sits beside the inherited one. Library code that calls equals calls the inherited version, which is true only when both variables refer to the same object, and your method never runs at all. Nothing in the compiler or at run time says a word.
Write @Override above every method you meant as an override. It changes nothing about how the program runs. It tells the compiler what you intended, and the compiler checks: if the header does not actually override anything in the parent, the file stops compiling and the error points at the line. Misspell toString as toStirng and without the annotation you get a brand new method nobody ever calls, plus output that looks like Student@1a2b3c and no explanation. With the annotation, the compiler reports the mistake on that line.
Which body runs
2 types are in play on these lines, and they do different jobs.
Plant p1 = new Cactus();
Plant p2 = new Fern("Boston fern");
The variable's type decides what you may call. The object's type decides which body runs. Java cannot settle the second question while compiling, because the same variable could hold a Cactus on one run and a Fern on the next. It settles it at the moment of the call. It starts at the object's own class and walks up toward the parent until it finds a body, and that body runs (dynamic dispatch).
So a method in the parent can call a method the parent has no body for.
public abstract class Plant
{
private String name;
private int waterDays;
// protected constructor as before
public abstract boolean isIndoor();
public String careNote()
{
if (isIndoor())
{
return this.name + " stays inside";
}
return this.name + " goes outside";
}
}
Plant does not know whether a plant lives indoors, and it does not need to. When careNote runs on a Cactus, the isIndoor() call inside it runs Cactus's version, which returns false. The same careNote on a Fern runs Fern's version, which returns true.
cactus goes outside
Boston fern stays inside
The shared sentence is written once, in the class that owns the fields. Each child supplies only the part that differs. Copying careNote into every child also works, and then every copy has to be kept the same by hand.
Calling the parent's version on purpose
Overriding does not throw the parent's version away. super.toString() reaches past your own method to the one you replaced, which is useful when your version is the parent's plus something.
public class GradStudent extends Student
{
private String advisor;
@Override
public String toString()
{
return super.toString() + ", advised by " + this.advisor;
}
}
The child does not repeat what the parent already knows how to print. Write this.toString() there instead of super.toString() and the method calls itself until the program stops with a StackOverflowError.
In the practice APE
The Inheritance problem uses this lesson. Its header comments are the specification.
Here is the same kind of task on the Plant family. Plant's toString returns the name, then ", water every ", then the number of days, then " days". Write Fern's toString from its comment.
// Fern: return what Plant's toString returns, followed by
// ", keep out of direct sun"
public String toString()
Answer
@Override
public String toString()
{
return super.toString() + ", keep out of direct sun";
}
For Plant p2 = new Fern("Boston fern");, System.out.println(p2); prints:
Boston fern, water every 3 days, keep out of direct sun
Fern cannot read Plant's private fields, so it asks Plant for its sentence with super.toString() and adds its own part. The variable's type is Plant, and Fern's toString still runs, because the object is a Fern.
Readiness check
Work each one on paper and write the answer down before you open it.
Plant is abstract, holds the careNote above, and declares isIndoor(). Cactus passes cactus and 14 up to Plant, and its isIndoor returns false. Fern passes the name it was given and 3 up, and its isIndoor returns true.
Plant p = new Fern("Boston fern");thenSystem.out.println(p.careNote());What prints, character for character?- Which class's
careNoteran, and which class'sisIndoorran? Plant p1 = new Cactus();thenSystem.out.println(p1.careNote());What prints?System.out.println(p1);What prints, and what madetoStringrun when nobody wrotep1.toString()?
Answers
1. Boston fern stays inside
2. Plant's careNote, because Fern does not override it. Fern's isIndoor, because the object is a Fern.
3. cactus goes outside. The same method ran, and the answer changed because the object did.
4. cactus, water every 14 days. println needs text from the object, so Java calls toString on it. Joining an object to a String with + does the same.
Each of these is written inside a class that extends another class.
public String toStirng(), with the name misspelled and no annotation. Does it compile? What doesprintlnprint for that object?- The same misspelling with
@Overrideabove it. What happens? public boolean equals(Student other)with@Overrideabove it. What happens, and why?public String toString(String prefix). Is this an override?
Answers
1. It compiles. It is a new method with a new name that nothing ever calls, so println falls back on Object's version and prints something like Student@1a2b3c.
2. The file does not compile, and the error points at that line and says the method does not override anything. That is the annotation doing the job you added it for.
3. The file does not compile, for the same reason. The inherited method takes an Object, so a version taking a Student is a different method with a different parameter list. Without the annotation it would compile and never run.
4. No. A different parameter list is a different method. Both can exist in the class at once, and only the one with no parameters is the one println reaches for.
Start from Plant, Cactus and Fern as above, with Fern's toString from the practice task. Add a third level:
public class TreeFern extends Fern
{
public TreeFern()
{
super("tree fern");
}
@Override
public boolean isIndoor()
{
return false;
}
}
Plant p3 = new TreeFern();thenSystem.out.println(p3.careNote());What prints?System.out.println(p3);What prints, and whosetoStringran?- Someone deletes
TreeFern'sisIndoormethod. Does it still compile, and what doesp3.careNote()return now? - How many
careNotemethods are written in those 4 classes?
Answers
1. tree fern goes outside. The isIndoor() call inside Plant reached TreeFern's version, 2 levels down.
2. tree fern, water every 3 days, keep out of direct sun. TreeFern has no toString, so the walk up from TreeFern finds Fern's first, and Fern's calls Plant's with super.
3. It still compiles, because TreeFern inherits Fern's isIndoor, so no promise is left unkept. p3.careNote() now returns tree fern stays inside. Nothing throws and nothing warns you.
4. 1, in Plant.
Further reading
- Object.toString (Java SE 19 & JDK 19). The method you are replacing, and the sentence saying it is recommended that every class override it.
- Object.equals (Java SE 19 & JDK 19). The parameter type that makes the careful-looking override above fail.
- Object (Java SE 19 & JDK 19). The whole class, so you can see the short list you inherit everywhere.
- Override (Java SE 19 & JDK 19). The annotation itself, and what the compiler promises to check.
- Overriding and Hiding Methods (The Java Tutorials). The rules for a matching header, and what happens to static methods, which behave differently.
- Polymorphism (The Java Tutorials). Oracle's own worked hierarchy for the variable type against the object type.
- Object as a Superclass (The Java Tutorials). Which inherited methods you are expected to override and which you are not.
- Method overriding (Wikipedia). The idea across languages, with the terms other languages use for it.
- Dynamic dispatch (Wikipedia). The mechanism underneath, including how it is actually implemented.
- Reading 15: Equality, from MIT 6.031, Software Construction. The long form of why the
equalsexample above is the one people get wrong. - Chapter 15. Expressions (The Java Language Specification, Java SE 19). Section 15.12.4 is the run-time method lookup, step by step.