Accessing Hidden Fields with the super Keyword in Java
Learn how the super keyword accesses hidden superclass fields in Java, the difference between field hiding and method overriding, and when to use super.field.
When a subclass declares a field with the same name as a field in its superclass, the subclass field hides the superclass field. Using the super keyword as super.field lets you reference that hidden field when you need the superclass version. This article explains how field hiding works, how super accesses the hidden field, and where this pattern can cause problems.
How Field Hiding Works
Field hiding occurs when a subclass declares a field with the same name as a field in its superclass. The two fields are independent storage locations; the subclass field does not override the superclass field, it merely hides it. Consider this example:
class Parent { String name = "Parent"; } class Child extends Parent { String name = "Child"; }
Here, Child has its own name field. The Parent field still exists, but any unqualified reference to name inside Child resolves to the subclass field. To access the hidden superclass field, you use super.name.
Using super to Access a Hidden Field
Inside an instance method of the subclass, super.fieldName refers to the field declared in the immediate superclass. For example:
class Parent { String name = "Parent"; } class Child extends Parent { String name = "Child"; void printNames() { System.out.println(name); // Child System.out.println(super.name); // Parent } }
Calling new Child().printNames() outputs:
Child Parent
The super keyword does not change Java's access rules. It can reach a hidden field only when the subclass can normally access that field—for example, a protected field, or a package-private field when the subclass is in the same package. A private field is not visible to subclasses, so super cannot bypass encapsulation; trying to access it causes a compile-time error.
When super.field Can Be Useful
Accessing a hidden field with super can be useful when a subclass needs to incorporate the superclass's state into its own logic. For instance, a subclass might extend a configuration object and need to refer to the original value during initialization or in a method that combines both fields.
class BaseConfig { int timeout = 1000; } class AdvancedConfig extends BaseConfig { int timeout = 5000; int getEffectiveTimeout() { // Use the subclass value if set, else fall back to the superclass value return this.timeout > 0 ? this.timeout : super.timeout; } }
But this pattern is rare. In well-designed code, fields are usually private and accessed through methods, so field hiding is less common. The primary use of super in Java is for constructor chaining and calling overridden methods, not field access.
Constructor Chaining and the super Keyword
The most frequent use of super is in constructors. A subclass constructor must call one of the superclass constructors, either implicitly or explicitly. The call must be the first statement in the constructor.
class Parent { private String name; Parent(String name) { this.name = name; } } class Child extends Parent { private int age; Child(String name, int age) { super(name); // must be the first statement this.age = age; } }
If you do not explicitly call super(...), the compiler inserts a call to the no-argument constructor of the superclass. If the superclass has no accessible no-argument constructor, the code will not compile.
super() is not the same as super.field; the former is a constructor invocation, while the latter is a field access. Both use the super keyword but serve different purposes.
Method Overriding vs. Field Hiding
It is important to distinguish method overriding from field hiding. Methods are polymorphic; fields are not. When you call an overridden method, the JVM resolves the method call at runtime based on the object's actual type. Field access, however, is resolved at compile time based on the declared type of the reference.
Consider:
class Parent { String name = "Parent"; void printName() { System.out.println(name); } } class Child extends Parent { String name = "Child"; @Override void printName() { System.out.println(super.name); // explicitly uses the Parent field } }
If you have a Parent reference pointing to a Child object and call printName(), the overridden method runs. If that method had not used super, it would reference the field based on the enclosing class, not the runtime type. This subtlety leads to bugs when developers expect fields to behave like methods.
Common Pitfalls and Misconceptions
One common mistake is expecting super to work outside an instance context, such as in a static method. Static methods do not have a this reference, so super is not allowed; it results in a compile-time error.
Another pitfall is assuming that super can access a field of a grandparent class directly. super always refers to the immediate superclass. If the immediate superclass also hides a field from a higher ancestor, you cannot use super.super.field—Java does not support such syntax. To reach a field two levels up, you need accessor methods or a different design.
Also note that super does not bypass encapsulation. If the superclass field is private, it is not accessible at all, even with super. The same access rules apply as for normal member access.
Performance and Maintainability Considerations
Field access with super has no meaningful runtime performance cost; the JVM resolves field accesses efficiently. The real cost is in maintainability. Overusing field hiding and super can make code confusing because the same field name refers to different variables depending on context. Debugging becomes harder when reading code because it is not immediately obvious which field is being referenced.
A better approach is to avoid field hiding altogether by:
- Giving subclass fields distinct names
- Using
privatefields and protected accessor methods (getters/setters) in the superclass - Using constructors or methods to pass the superclass state to the subclass when needed
That said, there are legitimate uses, such as when you are extending a third-party class where you cannot modify the superclass to add getters. In such cases, super.field provides a direct way to access the hidden field, but you must be careful about the access level and the immediate-superclass limitation.
Choosing Between super.field and Getters
When you need the superclass's field value, you have two options: direct access with super.field or calling a getter method. The choice depends on the situation:
- If the superclass exposes a public getter, use the getter. It respects encapsulation and allows the superclass to change its internal representation later.
- If no getter exists and the field is accessible to the subclass (
protected, package-private when the subclass is in the same package, orpublic),super.fieldis usually the most direct way to access the superclass version without modifying the superclass.
Consider the trade-offs:
| Approach | Encapsulation | Dependency on field name | Runtime cost | Best used when |
|---|---|---|---|---|
super.field | Weak | High | None | No getter available; field is protected/package |
| Getter method | Strong | Low | Minimal | Accessor exists; want to preserve flexibility |
| Distinct field name | Strong | None | None | You control both classes and can rename |
For most new code, prefer getters or renaming fields. Reserve super.field for cases where you are extending external or legacy code and cannot alter the superclass.
When Field Hiding Is Unavoidable
Sometimes field hiding arises in constrained code. You might be working with generated code or a framework where field names are prescribed and you cannot rename the superclass field. In those situations, super.field is the tool that lets you access the superclass's version of the hidden field.
Even then, you can mitigate confusion by:
- Documenting why the field is hidden
- Adding comments near the
super.fieldusage - Keeping the logic that accesses the hidden field minimal and localized
This reduces the chance of a future maintainer misreading the code. The cost is mostly cognitive, not runtime.