Overriding vs hiding: @Override, covariant returns & access rules
Instance methods are overridden at runtime; static methods and fields are hidden at compile time.
The critical distinction: override vs hide
Instance method overriding — when a subclass defines an instance method with the same signature as the parent, calls on a subclass object use the subclass version, regardless of the reference type. This is runtime dispatch.
Static method hiding — when a subclass defines a static method with the same signature, the method used is determined by the reference type at compile time, not the object. This is not polymorphism.
class Parent {
String describe() { return "Parent instance"; }
static String kind() { return "Parent static"; }
}
class Child extends Parent {
@Override
String describe() { return "Child instance"; } // overrides
static String kind() { return "Child static"; } // hides
}
Parent p = new Child();
System.out.println(p.describe()); // Child instance — runtime dispatch
System.out.println(p.kind()); // Parent static — compile-time reference type
Rules for a valid override:
- Same method name and parameter types.
- Return type must be the same or a covariant subtype (e.g., overriding Animal get() with Dog get() is valid).
- Access modifier may be the same or wider (e.g., package-private → public is fine; public → private is a compile error).
- Checked exceptions may be removed or replaced with a narrower checked exception — never a broader one.
@Override annotation instructs the compiler to verify these rules. Always use it; it turns a silent "I accidentally created a new method" bug into a compile error.