Topic 5.3
Method Overriding and @Override
In one line
Overriding means a subclass gives its own version of an inherited instance method with the same name and parameters. @Override asks the compiler to check that you really are overriding, which catches typos and wrong signatures.
Think of it like this
A school rule book says 'students go home at 3 pm'. The sports team's own rule book says 'team members go home at 5 pm'. For a team member, the team rule replaces the school rule; for everyone else the school rule still applies. That is overriding: same rule name, a more specific version for a more specific group.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Override
- Replace an inherited instance method with your own version that has the same name and parameter types.
- Method signature
- A method's name plus its parameter types, in order. The return type is not part of it.
- @Override
- An annotation asking the compiler to confirm this method overrides one from a parent class or interface.
- Annotation
- A label starting with
@that gives extra information to the compiler or tools.@Overrideis checked at compile time and then forgotten. - Covariant return type
- An overriding method may return a more specific type than the parent's method, e.g.
Cat baby()overridingAnimal baby(). - Method hiding
- What happens with static methods: a child's same-named static method hides the parent's, and the choice depends on the reference type, not the object.
- Overloading
- Several methods with the same name but different parameter lists. The compiler picks one using argument types.
Step by step
01Same signature, new body
Animal has String sound(). Cat declares String sound() too, with a different body. A Cat object now answers sound() with Cat's code; a plain Animal still uses Animal's. Nothing else in the program needs to change.
Notice the method in the child can be more visible than the parent's (protected → public) but never less: code that worked with an Animal reference must keep working when the object turns out to be a Cat.
class Animal {
protected String sound() { return "..."; }
}
class Cat extends Animal {
@Override
public String sound() { return "Meow"; } // wider access: allowed
}02The full rule list
Name and parameter types identical (after generic erasure; Topic 8.6). Parameter names don't matter.
Return type: identical for primitives and void; for reference types, the same type or a subtype (covariant).
Access: same or wider. Order from narrowest: private < package-private < protected < public.
Checked exceptions: the override may throw fewer or narrower checked exceptions, never new or broader ones. Unchecked exceptions are unrestricted.
Not overridable: final methods (compile error), static methods (hidden instead), private methods (invisible, so a same-named method is new), and constructors.
03Why @Override matters
Without @Override, a typo creates a new method silently. void speek() compiles happily next to the inherited speak(), and every call to speak() still runs the parent's version. The bug shows up later as wrong behaviour, not as an error.
With @Override, the compiler checks your claim immediately.
04Covariant returns and bridge methods
Cat baby() may override Animal baby(), because a Cat *is* an Animal, so callers expecting an Animal still get one. Callers who hold a Cat reference get a Cat back without casting.
The JVM, though, matches methods by name and exact return type (the method *descriptor*). So javac quietly generates a bridge method: a hidden Animal baby() in Cat that just calls Cat baby(). Old callers compiled against Animal find the bridge; javap -p shows both.
05Overloading is decided early, overriding late
Here's the most important distinction in this phase. The compiler sees only declared types. When you call p.print(x), it looks at the declared type of p and of x and picks which signature to call (overload resolution). That choice is baked into the bytecode.
At run time, the JVM looks at the actual object in p and picks whose implementation of that signature to run (override dispatch).
So: argument types are judged at compile time; the receiver object is judged at run time. Java has single dispatch: only the object before the dot is looked at dynamically, never the arguments.
06The equals(Point) trap
Object declares public boolean equals(Object o). If you write public boolean equals(Point o), you've overloaded it, not overridden it. Your version only runs when the compiler sees a Point argument. Collections like ArrayList.contains and HashMap call equals(Object), so they never see your method and fall back to identity comparison.
@Override on equals(Point) turns this silent bug into a compile error. Topic 4.8 shows the correct equals(Object) pattern.
07Static methods: hidden, not overridden
Static methods belong to the class, not to an object, so there's no object to dispatch on. If Parent and Child both declare static String who(), then parentRef.who() runs Parent.who() even when the object is a Child. The compiler even emits invokestatic Parent.who.
Calling a static method through an instance is legal but misleading; always call it through the class name (Child.who()).
Try it yourself
- 1
Add @Override to the buggy equals
In the last example, put
@Overrideabovepublic boolean equals(Point o). Compile and read the error. Then fix it properly:public boolean equals(Object o) { if (!(o instanceof Point)) return false; Point p = (Point) o; return p.x == x && p.y == y; }. All three lines should now printtrue. - 2
Predict before you run
In the Printer example, add
@Override void print(String s) { System.out.println("FancyPrinter.print(String)"); }to FancyPrinter. Predict all three lines, then run. Only the middle line changes. - 3
Try to narrow access
In the first example, change Cat's
public String toString()to plainString toString(). Compile and read the 'weaker access privileges' error.
Code & diagrams
Expected output
Meow
Cat
kitten: CatFancyPrinter didn't override print(String), so the inherited Printer version runs for the second call.
Expected output
FancyPrinter.print(Object)
Printer.print(String)
FancyPrinter.print(Object)Expected output
Parent.who (static)
Child.me (instance)
Child.who (static)ArrayList.contains calls equals(Object), which is still Object's identity check. Change the parameter to Object (and cast inside) to fix it.
Expected output
a.equals(b): true
a.equals(bAsObject): false
list.contains(b): falseBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Weaken access when overriding
Override toString() without public.
class Dog { @Override String toString() { return "Dog"; } }Break #2
Throw a broader checked exception
Override a method that throws nothing with one that throws Exception.
class A { void m() { } }
class B extends A { @Override void m() throws Exception { } }Break #3
Change the return type to something unrelated
Override Object get() with int get().
class A { Object get() { return null; } }
class C extends A { @Override int get() { return 1; } }Myth vs fact
Myth
@Override is required for overriding to work.
Fact
Overriding happens whether or not you write it. The annotation only makes the compiler check your intent. You should still always write it.
Myth
A child's static method overrides the parent's static method.
Fact
It hides it. The call is chosen by the reference's declared type, at compile time.
Myth
Changing only the return type creates an overload.
Fact
Return type isn't part of the signature for overloading. Same name and parameters with an incompatible return type is a compile error.
Myth
Java picks overloaded methods using the runtime type of the arguments.
Fact
Overload resolution uses only compile-time types. Only the receiver (the object before the dot) is dispatched at run time.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Bridge methods are flagged
ACC_BRIDGE | ACC_SYNTHETIC. They also appear for generics:class Name implements Comparable<Name>gets a bridgecompareTo(Object)that casts and callscompareTo(Name), which is where a strayClassCastExceptionin a raw-type call comes from. - ▸
JLS 8.4.8.3 defines the override rules; JLS 15.12 defines overload resolution in three phases: first without boxing or varargs, then with boxing, then with varargs. That's why
print(int)beatsprint(Integer)beatsprint(int...)for anintargument. - ▸
Package-private methods are only overridden by subclasses in the same package. A subclass in another package that declares the same method creates a new, unrelated method, and a parent method calling it still runs the parent's version. This surprises framework authors.
- ▸
Java's single dispatch is why the Visitor pattern exists: to choose behaviour by the runtime types of *two* objects you bounce the call twice (double dispatch). See the System Design course's Visitor pattern (
/topic/phase-2/visitor). Pattern matching for switch (Topic 11.2) is the modern alternative.
Remember this
- 1
To override, a subclass declares an instance method with the same name and the same parameter types as an inherited one. When code calls that method on an object of the subclass, the subclass's version runs, even if the variable's type is the parent (Topic 5.4).
- 2
The rules the compiler enforces: the return type must be the same, or a subtype for reference types (covariant return); the access can't be weaker (a
publicmethod stayspublic); the method may not declare broader checked exceptions (Topic 7.3); and the parent method must not befinal,staticorprivate. - 3
@Overrideis an annotation, a label for the compiler. It changes nothing at run time, but if the method doesn't actually override anything (a typo likespeek, or a different parameter type), compilation fails. Always write it. - 4
Overloading is not overriding. Overloading (Topic 3.4) means same name, *different parameters*, and the compiler picks the version by the compile-time types of the arguments. Overriding means same parameters, and the JVM picks the version by the runtime class of the object. Mixing them up causes the famous
equals(Point)bug. - 5
Static methods are hidden, not overridden. If both parent and child declare
static void who(), the call is chosen by the reference's compile-time type. Private methods aren't overridden at all: a same-named private method in the child is a brand-new, unrelated method.
Explain it without notes
List the rules an overriding method must follow.
What's the difference between overloading and overriding, in terms of when the decision is made?
Why does public boolean equals(Point p) break HashSet and ArrayList.contains?
What is a bridge method and why does javac create one for a covariant return?
Practice
Create Shape with String name() returning "shape" and Square overriding it with @Override. Then add a method name(String prefix) in Square. Print which ones are overrides and which is an overload by calling them.
Write Document with Document copy(), and Invoice extends Document with a covariant Invoice copy(). Show that invoice.copy() needs no cast.
Write a correct equals(Object) and hashCode() for Point(int x, int y) and show list.contains and set.size() behave correctly.
Trade-offs
- ↔
Overriding lets subclasses customise behaviour, but every overridable method is a promise you must keep: the parent's other methods may call it, and subclasses may rely on when it's called. Make methods
final(or the class final) unless you designed them to be overridden. - ↔
Covariant returns make subclass APIs nicer (no casts) at the cost of synthetic bridge methods, which can confuse reflection-based frameworks that list methods. Filter with
Method.isBridge().
Done when you can
Done when you can list every override rule and the error each one produces.
Done when you always write
@Overrideand know what it catches.Done when you can predict output that mixes overloading and overriding.
Done when you can explain static method hiding.
Done when you can explain covariant returns and bridge methods.