Command Palette

Search for a command to run...

PHASE 5Intermediate ~29 min· topic 3 of 13

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. @Override is 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() overriding Animal 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.

Main.javawhole filejava
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.

terminal
$ javac Main.java
── expected output ──
Main.java:2: error: method does not override or implement a method from a supertype
class Dog extends Animal { @Override void speek() { } }
^
1 error

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.

terminal
$ javap -p Dog
── expected output ──
Compiled from "Main.java"
class Dog extends Animal {
Dog();
Dog create();
Animal create();
}
Dog declared only `Dog create()`. The second, `Animal create()`, is the synthetic bridge javac added.

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.

Overloading is decided early, overriding latediagram
Rendering diagram…

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. 1

    Add @Override to the buggy equals

    In the last example, put @Override above public 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 print true.

  2. 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. 3

    Try to narrow access

    In the first example, change Cat's public String toString() to plain String toString(). Compile and read the 'weaker access privileges' error.

Code & diagrams

Overriding with wider access and a covariant return New tab
Sign in to run this example in your browser.

Expected output

Meow
Cat
kitten: Cat
Overload picked at compile time, override at run time New tab

FancyPrinter didn't override print(String), so the inherited Printer version runs for the second call.

Sign in to run this example in your browser.

Expected output

FancyPrinter.print(Object)
Printer.print(String)
FancyPrinter.print(Object)
Static methods are hidden New tab
Sign in to run this example in your browser.

Expected output

Parent.who (static)
Child.me (instance)
Child.who (static)
The equals(Point) overloading bug New tab

ArrayList.contains calls equals(Object), which is still Object's identity check. Change the parameter to Object (and cast inside) to fix it.

Sign in to run this example in your browser.

Expected output

a.equals(b):         true
a.equals(bAsObject): false
list.contains(b):    false

Break 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.

Main.javawhole filejava
class Dog { @Override String toString() { return "Dog"; } }
terminal
$ javac Main.java
── what you'll see ──
Main.java:1: error: toString() in Dog cannot override toString() in Object
class Dog { @Override String toString() { return "Dog"; } }
^
attempting to assign weaker access privileges; was public
1 error

Break #2

Throw a broader checked exception

Override a method that throws nothing with one that throws Exception.

Main.javawhole filejava
class A { void m() { } }
class B extends A { @Override void m() throws Exception { } }
terminal
$ javac Main.java
── what you'll see ──
Main.java:2: error: m() in B cannot override m() in A
class B extends A { @Override void m() throws Exception { } }
^
overridden method does not throw Exception
1 error

Break #3

Change the return type to something unrelated

Override Object get() with int get().

Main.javawhole filejava
class A { Object get() { return null; } }
class C extends A { @Override int get() { return 1; } }
terminal
$ javac Main.java
── what you'll see ──
Main.java:2: error: get() in C cannot override get() in A
class C extends A { @Override int get() { return 1; } }
^
return type int is not compatible with Object
1 error

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 bridge compareTo(Object) that casts and calls compareTo(Name), which is where a stray ClassCastException in 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) beats print(Integer) beats print(int...) for an int argument.

  • ▸

    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. 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. 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 public method stays public); the method may not declare broader checked exceptions (Topic 7.3); and the parent method must not be final, static or private.

  3. 3

    @Override is an annotation, a label for the compiler. It changes nothing at run time, but if the method doesn't actually override anything (a typo like speek, or a different parameter type), compilation fails. Always write it.

  4. 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. 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

01

List the rules an overriding method must follow.

02

What's the difference between overloading and overriding, in terms of when the decision is made?

03

Why does public boolean equals(Point p) break HashSet and ArrayList.contains?

04

What is a bridge method and why does javac create one for a covariant return?

Practice

01

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.

02

Write Document with Document copy(), and Invoice extends Document with a covariant Invoice copy(). Show that invoice.copy() needs no cast.

03

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 @Override and 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.