Detailed explanation
The output is 10 on the first line and 20 on the second line, assuming the executable statements are placed inside a method and the classes are in the same package. The key distinction is that Java resolves field access using the reference expression’s compile-time type, but dispatches overridden instance methods using the object’s runtime class. Here, obj has the declared type Base, while new Derived() creates a Derived object.
Both field declarations contribute separate instance fields to the object: Base.x is initialized to 10, and Derived.x is initialized to 20. The Derived declaration hides the inherited field; it does not replace it or override it. For obj.x, the compiler looks at the declared type Base and selects Base.x. Consequently, the first println prints 10, even though obj refers to a Derived instance. Calling this resolution “static” describes when the field is selected; it does not mean that x is a static field.
For obj.getX(), compilation first checks that an accessible getX() method exists on Base. Because this is an ordinary overridable instance method, runtime dispatch then selects Derived.getX(). Inside that implementation, the unqualified expression x refers to Derived.x, so the method returns 20. At the bytecode level, ordinary field reads use getfield with a symbolic field reference, whereas this method call uses invokevirtual, whose invocation semantics select the overriding implementation for the receiver. Field selection does not perform equivalent runtime override dispatch.
Interviewers use this example to test whether you distinguish reference type, object type, field hiding, and method overriding rather than applying “runtime polymorphism” to every member access. A useful check is to remove Derived.getX(): the inherited Base.getX() then returns 10 because its body refers to Base.x, even when invoked on a Derived object. Conversely, ((Derived) obj).x reads Derived.x and produces 20. That cast changes the expression’s compile-time type, not the object itself. In production code, hiding fields with identical names often reduces readability; private fields and clearly defined methods usually express the design more safely.
Key points to remember
- Output: 10 followed by 20, each on a separate line.
- obj.x selects Base.x because obj has compile-time type Base.
- A Derived instance contains distinct Base.x and Derived.x fields.
- obj.getX() dynamically dispatches to Derived.getX(), which returns Derived.x.
- Fields are hidden, not overridden; runtime polymorphism applies to overridden instance methods.
Common mistakes candidates make
- Predicting 20 for both lines because the runtime object is Derived.
- Treating field hiding as overriding or assuming Derived.x replaces Base.x.
- Confusing statically resolved field access with the static modifier.
Likely follow-up questions
What happens if Derived does not override getX()?
Both lines print 10. The inherited Base.getX() reads Base.x because field references inside its body are resolved in Base, not according to the receiver’s runtime class.
Does casting obj change which members are accessed?
((Derived) obj).x reads Derived.x and returns 20. However, ((Base) obj).getX() still invokes Derived.getX(); casting does not disable dynamic dispatch.
How can Derived access the Base versions of these members?
Inside an instance method of Derived, super.x accesses Base.x, and super.getX() explicitly invokes Base.getX(). In this example, both expressions evaluate to 10.
Interview tip: State the two output lines first, then explain each using the reference type for fields and the runtime object type for overridden methods.
