How do overriding, overloading, and method hiding differ, and how does each get resolved?
answer
- Override = runtime, object type, same signature, instance
- Overload = compile time, static arg types, different params
- Hide = static methods + fields, reference type
- Only instance methods are truly polymorphic
- @Override catches accidental overloads/hides
basics
~20 sOverriding replaces an inherited instance method with the same signature and is chosen at runtime by the object's real type. Overloading is several methods with the same name but different parameters, chosen at compile time. Hiding happens with static methods (or fields) and is resolved by the reference type, not the object.
solid answer
~50 sThese three are easy to confuse but resolved differently. Overriding: a subclass redefines a non-static, non-final instance method with the same signature; the JVM picks the implementation at runtime based on the object's actual type (dynamic dispatch). Overloading: multiple methods in the same scope share a name but differ in parameter lists; the compiler picks one at compile time from the static types of the arguments. Hiding: a static method (or any field) with the same name in a subclass does not override - it hides; calls resolve by the compile-time (reference) type, not the runtime object. So `Parent p = new Child()` calls Child's overridden instance method, but Parent's hidden static method and Parent's field. Use @Override to assert you meant to override; it errors if you accidentally overloaded (wrong params) or tried to 'override' a static method.
code
java · 15 linesclass Parent {
static String who() { return "parent"; } // static -> can be hidden
String sound() { return "..."; } // instance -> can be overridden
int x = 1; // field -> hidden, not overridden
}
class Child extends Parent {
static String who() { return "child"; } // HIDES Parent.who
@Override String sound() { return "meow"; } // OVERRIDES
int x = 2; // HIDES Parent.x
}
Parent p = new Child();
p.sound(); // "meow" -> overriding: runtime type Child
p.who(); // "parent" -> hiding: reference type Parent
p.x; // 1 -> field: reference type Parentgo deeper
Can give the one-line distinction: override = same params, overload = different params; aware static is special.
Clearly separates all three, names the resolution time/basis for each, and recognizes the field-hiding trap.
Explains dynamic vs static binding precisely, walks the Parent p = new Child() resolution for method/static/field, and cites the accidental-overload-of-equals trap.
Reasons about why fields and statics are non-virtual by design (binary compatibility, performance), the dangers of hiding in API design, and how @Override and tooling enforce intent across large codebases.
## Three mechanisms that look similar All three involve methods sharing a name, but they are governed by different rules and resolved at different times. ### 1. Overriding (runtime, instance methods) A subclass provides a new body for an inherited **instance** method with the **same signature** (name + parameters). The choice of which implementation runs is made **at runtime** by **dynamic dispatch**: the JVM looks at the object's *actual* (runtime) class, not the declared reference type. ```java class Animal { String sound() { return "..."; } } class Cat extends Animal { @Override String sound() { return "meow"; } } Animal a = new Cat(); a.sound(); // "meow" - runtime type Cat wins ``` ### 2. Overloading (compile-time, same name, different params) **Overloading** is declaring several methods with the **same name** but **different parameter lists** in the same accessible scope (same class or inherited). They are *unrelated* methods that merely share a name. The compiler selects which one to call **at compile time**, based on the **static (declared) types** of the arguments, using overload resolution (most-specific applicable method). ```java void print(int x) {} void print(Object x) {} Object o = 5; print(o); // calls print(Object) - chosen from o's STATIC type, even though it holds an Integer print(5); // calls print(int) ``` Overloading is sometimes called 'compile-time polymorphism' or 'static binding'. ### 3. Hiding (static methods and fields) **Static methods cannot be overridden.** If a subclass declares a static method with the same signature as a parent static method, it **hides** it. Hidden members are resolved by the **compile-time / reference type**, NOT the runtime object. ```java class Parent { static String who() { return "parent"; } } class Child extends Parent { static String who() { return "child"; } } Parent p = new Child(); p.who(); // "parent" - resolved by reference type Parent (hiding, not overriding) ((Child) p).who(); // "child" ``` **Fields are also hidden, never overridden.** Field access binds to the **declared type** of the reference: ```java class Parent { int x = 1; } class Child extends Parent { int x = 2; } Parent p = new Child(); p.x; // 1 - field resolved by reference type Parent ((Child) p).x; // 2 ``` ## The decisive contrast | Aspect | Overriding | Overloading | Hiding | |---|---|---|---| | What matches | same signature | same name, different params | same signature (static) / same name (field) | | Applies to | instance methods | any methods | static methods, fields | | Resolved | runtime (object type) | compile time (arg static types) | compile time (reference type) | | Polymorphic? | yes (dynamic) | no | no | ## Why @Override matters here Because overloading and a botched override look identical in source, a tiny mistake - wrong parameter type, an extra parameter, or trying to 'override' a static method - silently produces an *overload* or a *hide* instead of the override you intended, and the bug surfaces only at runtime as 'the wrong method ran'. Annotating with `@Override` makes the compiler verify you genuinely overrode an instance method; if you accidentally overloaded or targeted a static, it refuses to compile. ## Common trap ```java class Base { boolean equals(Base other) { ... } } // OVERLOAD of Object.equals, not an override! ``` This declares `equals(Base)` - a new overload - while `Object.equals(Object)` remains unoverridden. Collections calling `equals(Object)` never hit this method. `@Override` would have flagged it instantly.
- Given `Parent p = new Child();`, why does `p.sound()` use Child's version but `p.x` uses Parent's field?Instance methods are dynamically dispatched - resolved by the object's runtime type (Child). Fields are statically bound - resolved by the reference's declared type (Parent). Fields are hidden, not overridden, so they never participate in polymorphism.
- Is overloading a form of polymorphism?It is sometimes called 'compile-time' or 'ad-hoc' polymorphism, but it involves no dynamic dispatch - the method is fixed at compile time from the arguments' static types. True (runtime/subtype) polymorphism comes only from overriding.
Overriding is a stunt double standing in for an actor - whoever is actually present plays the scene (runtime). Overloading is picking which tool to grab based on the label you read in advance (compile time). Hiding is two name tags reading the same name; you go by the badge on the door you walked through (reference type), not who is really inside.
saying these in an interview costs you the question
- Saying static methods can be overridden - they are hidden, resolved by reference type.
- Thinking fields are polymorphic - field access binds to the declared type, always.
- Believing overload resolution uses the runtime type of arguments - it uses static types at compile time.
- Confusing 'same name' (overload) with 'same signature' (override).