Method Overriding in Java: Rules, Overloading and Hiding
Method overriding in Java replaces an inherited method in a subclass. The override rules, @Override, covariant returns, and how overloading and hiding differ.
- Course: Java study plan
- Module: OOP II — inheritance & polymorphism
- Kind: Lesson
- Reading time: 14 min
- Runtime: OpenJDK 18
What is method overriding in Java?
Method overriding in Java is a subclass declaring an instance method with the same name and parameter types as one it inherits, so calls on its objects run the subclass version whatever the reference's declared type. A valid override keeps or widens access, may return a subtype, throws no new or broader checked exceptions, and cannot replace a static, private or final method.
Lesson
A subclass overrides a parent method by declaring one with the same signature; calls on the object then run the subclass version, whatever the reference's declared type. The rules for a valid override are exact — the compiler enforces some and warns about none of the rest — and the difference between overriding and hiding is a reliable interview question. This lesson is the checklist.
The rules of a valid override
Given class Sub extends Base and a method m in Base:
| Rule | Detail |
|---|---|
| Same name and parameter types | Exactly. Different parameters = an overload, not an override. |
| Return type | Same, or a subtype for reference returns (covariant return: Base.copy() returns Base, Sub.copy() may return Sub). Primitive returns must match exactly. |
| Access | Not more restrictive. public in the parent must stay public; protected may become public. |
| Checked exceptions | May throw fewer or narrower checked exceptions, never new or broader ones (Module 12). Unchecked ones are free. |
Not final, not static, not private | A final method cannot be overridden; a static one is hidden, not overridden; a private one is invisible, so a same-named method in the subclass is unrelated. |
| Instance ↔ instance | An instance method cannot override a static one or vice versa. |
class Base {
protected Object make() throws IOException { … }
}
class Sub extends Base {
@Override
public String make() { … } // valid: wider access, covariant return, fewer exceptions
}
@Override
Always annotate an override. The annotation asks the compiler to verify the method overrides something; a typo in the name (tostring), a wrong parameter type (equals(Point p) instead of equals(Object o)), or a final parent method then fails to compile instead of silently creating a new method that nobody calls. equals(Point) is the classic: it compiles, HashSet never calls it, and the bug appears months later.
Calling the parent version
@Override
public String toString() {
return super.toString() + "[extra]";
}
super.m() invokes the parent's implementation on the same object. Common in toString, in equals for subclasses that add fields, and in hook methods that extend rather than replace behaviour.
Overriding versus overloading
class Printer {
void print(Object o) { System.out.println("object"); }
}
class FancyPrinter extends Printer {
void print(String s) { System.out.println("string"); } // OVERLOAD — different parameter type
@Override void print(Object o) { System.out.println("fancy object"); } // OVERRIDE
}
Printer p = new FancyPrinter();
p.print("hi"); // "fancy object" — the reference type Printer has only print(Object); that is chosen at compile time
// and then dispatched to FancyPrinter's override at run time
((FancyPrinter) p).print("hi"); // "string" — now the overload set includes print(String)
Two decisions happen: the compiler picks the overload from the reference's static type (Module 5); the JVM picks the override from the object's runtime class. Keep both in mind when a call does something unexpected.
Static methods are hidden, not overridden
class A { static String who() { return "A"; } }
class B extends A { static String who() { return "B"; } }
A ref = new B();
ref.who(); // "A" — resolved by the STATIC type; static methods do not dispatch
B.who(); // "B"
B.who hides A.who. There is no polymorphism for statics; calling them through an instance reference is legal and misleading, which is why style checkers reject it. @Override on a static method is a compile error — the compiler's way of saying so.
Fields are hidden too
class A { String name = "A"; }
class B extends A { String name = "B"; }
A a = new B();
a.name // "A" — fields are resolved by static type, never overridden
((B) a).name // "B"
An object of class B has two name fields. This is confusing enough that redeclaring a parent's field is treated as a bug. Fields are private; behaviour is overridden through methods.
Overriding Object methods
toString, equals, hashCode are the three every class overrides eventually (lesson 4). equals must take Object — public boolean equals(Object o) — or it is an overload that collections ignore. clone and finalize are legacy; do not override them.
Calling overridable methods from constructors
class Base {
Base() { init(); } // calls the override before Sub's fields exist
void init() { }
}
class Sub extends Base {
private final List<String> items = new ArrayList<>();
@Override void init() { items.add("x"); } // NullPointerException: items is still null here
}
The parent constructor runs before the subclass's field initialisers (Module 7), so an override invoked from it sees default field values. Rule: constructors call only private, static or final methods. Frameworks that need post-construction hooks provide an explicit init() called after new.
Covariant returns and fluent APIs
class Builder { Builder name(String n) { …; return this; } }
class FancyBuilder extends Builder {
@Override FancyBuilder name(String n) { super.name(n); return this; } // covariant: callers keep the Fancy type
FancyBuilder colour(String c) { … }
}
new FancyBuilder().name("x").colour("red"); // compiles only because name() returns FancyBuilder
Covariant returns exist for exactly this — and for clone() returning the precise type.
Exceptions in overrides
If Base.load() throws IOException, Sub.load() may throw IOException, a subclass of it, or nothing — but not Exception or SQLException. Callers holding a Base reference catch IOException; a broader exception would escape their handlers. Unchecked exceptions are not part of the signature and may be added freely.
Interview angle
- "Can you override a static method?" No — hiding. "A private one?" No — invisible. "A final one?" No.
- "Can an override return a different type?" A subtype of the parent's reference return type.
- "Why must
equalstakeObject?" Otherwise it overloads instead of overriding and collections do not call it. - "Can an override be more restrictive?" No: access may widen, never narrow.
- "What does
@Overridedo at run time?" Nothing; it is a compile-time check.
Key takeaways
- Same signature, compatible (covariant) return, no narrower access, no broader checked exceptions; not for
static,privateorfinalmethods. - Always
@Override;equals(Object), notequals(MyType). - Overloads are chosen by the compiler from the static type; overrides by the JVM from the runtime class.
- Static methods and fields are hidden, not overridden — resolved by reference type.
- Never call overridable methods from a constructor.
Common questions
What is the difference between overloading and overriding in Java?
Overloading declares methods with the same name but different parameter types, and the compiler picks one from the arguments' static types. Overriding redeclares an inherited method with the same signature, and the JVM picks the version from the object's runtime class. Overloading is compile-time polymorphism; overriding is run-time.
Can you override a private or final method in Java?
No. A final method cannot be overridden at all; a private method is invisible to subclasses, so a same-named method there is unrelated; and a static method with the same signature hides the parent's instead of overriding it, resolved by the reference's declared type.
What does @Override do in Java?
It asks the compiler to check that the method really overrides a supertype method, so a typo in the name, a wrong parameter type such as equals(Point p), or a final parent method becomes a compile error. It does nothing at run time.
What is a covariant return type in Java?
An overriding method may return a subtype of the parent method's reference return type: if Base.copy() returns Base, Sub.copy() may return Sub. Callers holding the subclass keep the precise type, which fluent builders rely on. Primitive return types must match exactly.
Why should you not call overridable methods from a constructor?
The parent constructor runs before the subclass's field initialisers, so an override called from it sees the subclass's fields at their defaults — a list field is still null and the call throws. Constructors should call only private, static or final methods.
Exercises
Override or overload?
Complete the program so its output demonstrates the difference between overriding and overloading. Printer has describe(Object o) returning "object" and static String kind() returning "base". FancyPrinter overrides describe(Object) to return "fancy object", adds an overload describe(String s) returning "fancy string", and hides kind() with one returning "fancy".
main (already written) reads a line of text and prints four lines using a Printer-typed reference to a FancyPrinter, then a FancyPrinter-typed reference. Make the classes so the expected output appears.
Expected output for any input line:
fancy object
fancy string
base
fancytoString up the chain
Write class Person (name) with toString() returning Person[name=<name>]; class Employee extends Person (salary) whose override returns super.toString() with , salary=<salary> inserted before the closing bracket; and class Manager extends Employee (reports) doing the same with , reports=<n>. Mark every override with @Override. Read n lines <kind> <name> [salary] [reports] and print each object through a Person reference — the most specific toString runs.
Example: 3 then person ann, employee bob 5000, manager cy 9000 4 →
Person[name=ann]
Person[name=bob, salary=5000]
Person[name=cy, salary=9000, reports=4]In this module: OOP II — inheritance & polymorphism
- Extending classes — super, constructors and what is inherited
- Overriding — the rules and the traps (this lesson)
- Polymorphism, casting and instanceof
- The Object class — equals, hashCode and toString
- Abstract classes and the template method
- When not to inherit — composition, Liskov and final
- Checkpoint — Inheritance & polymorphism
← Extending classes — super, constructors and what is inherited · Polymorphism, casting and instanceof →