Overriding a method: the four checks¶
☕ Java 17 · Rewriting What Your Parent Class Does · 1/3
Three choices for an inherited method¶
| Choice | How | Result |
|---|---|---|
| Keep it | write nothing | the child uses the parent's version |
| Replace it | declare the same method in the child | overriding: the child's version runs |
| Lock it | the parent marks it final |
no child can replace it |
Shared code is written once in the parent; each child rewrites only what differs.
Replacing a parent method¶
class Subscription {
public int getPrice() {
return 10;
}
}
class PremiumPlan extends Subscription {
public int getPrice() {
return super.getPrice() + 8;
}
}
new Subscription().getPrice() returns 10; new PremiumPlan().getPrice() returns 18.
super.getPrice() calls the parent's version. Forget super and write getPrice() + 8: the method calls itself forever, and the program crashes with a StackOverflowError.
The real object decides which version runs, even through a parent-typed variable: Subscription s = new PremiumPlan(); s.getPrice() returns 18.
The four checks¶
Every override must pass four compiler checks:
| # | Check | Example |
|---|---|---|
| 1 | Same signature: name + parameter types | play() vs play(int speed) is an overload, not an override |
| 2 | Same access or more open | protected → protected or public, never package or private |
| 3 | No new or broader checked exception | throws FileNotFoundException → the same, a narrower one, or none |
| 4 | Same return type, or a subtype | CharSequence getTitle() → String getTitle() is fine |
The logic: code that uses the parent type must be able to trust the parent's promises. A child may give more, never less.
Access and exceptions in practice¶
class Content {
public void play() { }
void load() throws FileNotFoundException { }
}
class Movie extends Content {
protected void play() { } // ❌ narrower than public
void load() throws IOException { } // ❌ broader checked exception
}
The fixed version keeps public and drops the exception:
class Movie extends Content {
public void play() { } // ✅
void load() { } // ✅ throwing nothing is allowed
}
An override may always add unchecked exceptions (like IllegalStateException): check 3 only concerns checked ones.
Return types can get more specific¶
class Content {
public CharSequence getTitle() { return "Dune"; }
public Number getYear() { return 2021; }
}
class Movie extends Content {
public String getTitle() { return "Dune"; } // ✅ String is a CharSequence
public Integer getYear() { return 2021; } // ✅ Integer is a Number
}
This is a covariant return type. The reverse fails: if the parent returns String, the child can't return CharSequence. Quick test: does the child's value fit the parent's return type without a cast? And void only matches void.
These checks apply to instance methods. Static methods, fields and private methods follow different rules.