skip to content

Why is method/field hiding considered a code smell, and what design guidance follows from how Java resolves hidden members?

level: principalimportance: nice to knowfreq 26%

answer

  1. Hiding looks like overriding but binds by declared type
  2. Upcast changes hidden members → breaks least surprise & LSP
  3. Private fields + getters restore dynamic, substitutable behavior
  4. No statics down a hierarchy; call by class name
  5. @Override is the compiler-enforced guard against accidental hides

basics

~20 s

Hiding looks like overriding in the source but behaves oppositely — it's resolved by the declared type, not the object — so it surprises readers and causes subtle bugs. Guidance: don't redeclare static methods or shadow fields; keep fields private and expose behavior through overridable methods.

solid answer

~50 s

Hiding is a smell because it breaks the reader's mental model: same-named members read like polymorphism but resolve statically by the declared type, so behavior changes when you upcast — a violation of the principle of least surprise and a hazard for Liskov substitutability. A subclass that hides a static method or shadows a field can make a parent reference behave differently from a child reference to the *same* object, which silently breaks callers written against the supertype. The guidance: never redeclare statics down a hierarchy (call them by class name); avoid same-named fields and keep state private, exposing it through getters/setters that genuinely override and dispatch dynamically; always annotate intended overrides with @Override so the compiler rejects an accidental hide; and where varying behavior is needed, model it with instance-method polymorphism (or composition/strategy) rather than shadowing. These keep the supertype contract honest and protect library evolution, where a later same-named member in a base class can accidentally shadow subclass members.

go deeper

for a junior

Can state 'avoid hiding, prefer overriding' as a rule of thumb without the deeper substitutability rationale.

for a middle

Explains why hiding surprises readers (declared-type binding) and applies the private-fields-plus-getters fix.

for a senior

Connects hiding to least-surprise and basic LSP concerns and enforces @Override and no-static-redeclaration conventions in code review.

for a principal

Frames hiding as a substitutability and fragile-base-class risk, sets team standards (encapsulated state, instance-method polymorphism, lint rules), and reasons about library-evolution failure modes when base classes gain same-named members.

## Why hiding is a smell **Hiding** (static methods and fields) and **overriding** (instance methods) are written almost identically — a subclass member with the same name as a superclass one — but resolve by **opposite** rules: - **Overriding** ⇒ chosen by the object's **runtime type** (dynamic dispatch). Upcasting to the supertype still runs the subtype's behavior. This is *substitutable* and predictable. - **Hiding** ⇒ chosen by the expression's **compile-time (declared) type** (static binding). Upcasting *changes* which member you get. Because the two look the same in code, a reader naturally assumes polymorphism. When the member is actually hidden, the behavior flips on upcast — the **principle of least surprise** is violated, and the bug is invisible at the call site. ### The Liskov-substitution angle The **Liskov Substitution Principle (LSP)** says a subtype must be usable anywhere its supertype is expected, without changing correctness. Hiding undermines this: a `Parent`-typed variable holding a `Child` can read the parent's field or run the parent's static method, while a `Child`-typed variable reads the child's. The *same object* behaves differently depending on the reference type, so code written against the supertype can silently diverge from the subtype's intent — a substitutability failure that dynamic dispatch (overriding) does not have. ### The library-evolution hazard Hiding also bites during evolution. If a base class is later given a new field or static method whose name happens to match one a subclass already declares, the subclass member is suddenly *hiding* the new base member (or vice versa). Because resolution is by declared type, existing callers can change behavior without any obvious source change in the subclass — a fragile-base-class problem specific to statically bound members. ### Concrete guidance 1. **Never redeclare static methods down a hierarchy.** If you need per-subtype behavior, use **instance methods** (which override) — possibly via a factory or strategy object. Always invoke statics by **class name**, never through an instance reference (which reads like a polymorphic call but isn't). 2. **Don't shadow fields.** Keep state **private** and expose it through **getters/setters**. Accessors are instance methods, so they genuinely override and dispatch dynamically — restoring the intuitive 'subclass value wins through a supertype reference' behavior. Private fields also can't be confusingly shadowed. 3. **Always write `@Override` on intended overrides.** The compiler rejects it on a static method, a signature mismatch, or a field, turning an accidental hide/typo into a compile error. 4. **Prefer composition/strategy where variation is real behavior.** If subtypes truly differ, model the difference as polymorphic behavior, not as shadowed data or static lookups. 5. **Treat any same-named static/field across a hierarchy as a review red flag.** Static analysis tools (e.g. linters, IDE inspections) can warn on field hiding and on static calls through instances — enable them. ### The underlying principle > Make the *only* same-named-member mechanism in your hierarchies be **overriding of instance methods**, which is substitutable and dispatches dynamically. Avoid the two statically bound forms (static-method hiding, field shadowing) because they break the supertype contract and surprise readers. ### Summary rule > Hiding ≠ overriding: it binds by declared type, breaks substitutability, and surprises readers. Encapsulate fields behind overridable accessors, keep statics off the hierarchy, and let `@Override` enforce the boundary.

  • How does hiding violate the Liskov Substitution Principle in practice?
    With hiding, the same object behaves differently through a supertype reference versus a subtype reference (the parent's field/static is read for the supertype). Code written against the supertype therefore can't rely on the subtype's intended behavior, breaking substitutability — unlike overriding, which is identical through either reference.
  • Why is exposing state through getters safer than protected/public fields in a hierarchy?
    Getters are instance methods, so an overridden getter dispatches dynamically and a supertype reference sees the subtype's value — the intuitive result. Public/protected fields can be shadowed, producing declared-type-dependent reads; making fields private removes the shadowing hazard entirely.

saying these in an interview costs you the question

  • Treating field shadowing or static redeclaration as a valid polymorphism tool
  • Believing hiding preserves Liskov substitutability
  • Skipping @Override on intended overrides
  • Exposing public mutable fields that subclasses then shadow

context