skip to content

Which kinds of members in Java are statically bound and which are dynamically bound? Walk through each case.

level: middleimportance: must knowfreq 60%

answer

  1. Only overridable instance methods are dynamic
  2. Fields, static, private, final → static binding
  3. static method in subclass = hiding; field same name = shadowing
  4. Overload = compile-time signature pick; override = run-time target pick
  5. Ask: could a subclass override it? yes → virtual

basics

~20 s

Dynamically bound (resolved at run time by the object's real type): ordinary overridable instance methods. Statically bound (resolved at compile time by the declared type): fields, static methods, private methods, and final methods. Overload selection is also done at compile time.

solid answer

~50 s

The rule of thumb: only ordinary, overridable instance methods use dynamic (late) binding — the JVM dispatches them on the object's actual runtime type via invokevirtual/invokeinterface. Everything else is statically bound at compile time using the reference's declared type. That includes: fields (never polymorphic — accessed via the compile-time type), static methods (resolved by declared type; a subclass version hides rather than overrides), private methods (invisible to subclasses, so no override possible — invokespecial), and final methods (cannot be overridden, so the target is fixed). Separately, choosing among overloaded signatures is always a compile-time decision based on the static argument types. A useful mental model: if a subclass could legally override it and the override could differ, the call is virtual and dynamic; if overriding is impossible (static/private/final) or it's not a method at all (a field), it's static.

go deeper

for a junior

Can say overridden instance methods are dynamic and fields are not; may not enumerate every static-bound case.

for a middle

Enumerates the full list (fields, static, private, final → static; virtual instance methods → dynamic) and distinguishes hiding/shadowing from overriding.

for a senior

Maps each case to the invoke* bytecode and explains overload-vs-override as orthogonal compile-time/run-time decisions.

for a principal

Reasons about devirtualization of final/effectively-monomorphic calls and the design trade-offs of non-polymorphic fields and statics.

## The single rule and its cases Think of one question per member: *could a subclass override this so the override might run instead?* If yes, the call is **virtual** and **dynamically bound**. If no, it is **statically bound**. ### Dynamically bound - **Overridable instance methods** — non-static, non-final, non-private instance methods. The compiler emits `invokevirtual` (class reference) or `invokeinterface` (interface reference). At run time the JVM looks up the actual object's class and runs the most-derived override. This is the *only* member kind with dynamic dispatch, and it's the basis of runtime polymorphism. ### Statically bound - **Fields** — Java fields are *never* polymorphic. `ref.field` is resolved with `getfield`/`getstatic` against the **compile-time type** of `ref`. If a subclass declares a field of the same name, it *shadows* (hides) the parent field; which one you see depends on the reference's declared type, not the object. - **`static` methods** — resolved by the declared type with `invokestatic`. A same-signature static method in a subclass **hides** the parent's; it does not override. `Parent p = new Child(); p.stat();` calls Parent's static method. - **`private` methods** — not inherited/visible to subclasses, so they cannot be overridden. Invoked via `invokespecial`. (A subclass method with the same name is an unrelated new method.) - **`final` methods** — overriding is forbidden by the compiler, so the target is fixed and the JVM/JIT can treat it as non-virtual. (The bytecode may still be `invokevirtual`, but the target cannot vary, so it's effectively static binding and trivially devirtualized.) - **Constructors** and **`super.method()`** calls — non-virtual, `invokespecial`. ### Overload resolution (compile time, separate axis) Picking among `f(int)`, `f(long)`, `f(Object)` is **overload resolution**, done entirely by the compiler from the **static types** of the arguments. This is orthogonal to override dispatch: the compiler picks the *signature*, then (for virtual methods) the JVM picks the *override of that signature* at run time. ## A worked example ```java class A { static String who() { return "A.static"; } String name() { return "A.name"; } String id = "A.id"; } class B extends A { static String who() { return "B.static"; } // hides @Override String name() { return "B.name"; } // overrides String id = "B.id"; // shadows } A ref = new B(); ref.name(); // "B.name" -> dynamic (override runs) ref.id; // "A.id" -> static (compile-time type A) A.who(); // "A.static" -> static method, by declared type ``` ## Why it matters in practice - Relying on a field to be "overridden" is a classic bug — use a getter (a method) if you want subclass specialization. - Calling a `static` method through an instance reference is misleading; tools warn about it precisely because it is statically bound by the declared type. - Marking a method `final` is both a design statement (no override) and an enabler of guaranteed devirtualization.

  • Why can't private methods be overridden, and what binding do they use?
    Private methods are not visible to subclasses, so a subclass can't override them — a same-name method is unrelated. They are invoked with invokespecial, i.e. statically bound.
  • Is overload resolution static or dynamic, and why?
    Static. The compiler selects the overloaded signature from the declared/static types of the arguments at compile time; the runtime types of arguments don't change which overload is chosen.

saying these in an interview costs you the question

  • Thinking shadowed fields are chosen by the object's runtime type
  • Calling subclass static methods 'overrides' (they hide)
  • Believing private or final methods participate in dynamic dispatch
  • Conflating overload resolution with override dispatch

context