Java Inherited Members: Fields, Methods, and Access Rules
java inherited members: Understand how Java inherited members work: which fields and methods pass to subclasses, how overriding and hiding differ, and how access modif...
java inherited members requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
In Java, inherited members are the fields and methods that a subclass receives from its superclass. When a class extends another class, it gains access to the non-private members of the parent, subject to access modifiers. Understanding exactly which members are inherited, how they behave, and where they can be overridden is essential for designing class hierarchies that are both flexible and predictable.
What Counts as an Inherited Member
A subclass inherits all accessible fields and methods from its superclass. Accessibility is determined by the access modifier: public and protected members are always inherited, while package-private members are inherited only if the subclass is in the same package. Private members are not inherited at all—they exist in the parent object but are not visible to the subclass. This distinction is often the first source of confusion because a subclass may have a field with the same name as a private field in the parent, but that field is a separate declaration, not an override.
Consider this example:
public class Animal { protected String name; private int age; public void eat() { System.out.println("Eating"); } } public class Dog extends Animal { public void showName() { System.out.println(name); // accessible, inherited } public void showAge() { System.out.println(age); // compile error: age is private } }
The name field and eat() method are inherited. The age field is not inherited, even though it exists in the memory of a Dog object. This rule is fundamental to understanding how encapsulation interacts with inheritance.
Inherited Fields: Visibility and Shadowing
Fields are inherited with their exact type and visibility. A subclass can use an inherited field directly, but it cannot reduce its visibility. If a subclass declares a field with the same name as an inherited field, it does not override the parent field; it shadows it. Shadowing means the subclass now has two fields with the same name: one from the parent and one from the child. The reference type determines which one is accessed.
public class Parent { protected int value = 10; } public class Child extends Parent { protected int value = 20; // shadows parent's value public void printValues() { System.out.println(value); // 20, child's field System.out.println(super.value); // 10, parent's field } }
Shadowing is rarely desirable. It introduces ambiguity and can lead to subtle bugs if you accidentally refer to the wrong field. In practice, avoid redeclaring fields with the same name unless you have a deliberate reason, such as changing the type in a generic context. Even then, consider renaming the field or using a different design.
Inherited Methods: Overriding and Hiding
Methods are inherited and can be overridden. Overriding allows a subclass to provide a new implementation for an instance method that is already defined in the parent. The method signature must match exactly—same name, parameter list, and return type (or a covariant return type). Access cannot be reduced, but it can be widened. For example, a protected method can be made public in the subclass.
public class Shape { public double area() { return 0; } } public class Circle extends Shape { private double radius; @Override public double area() { return Math.PI * radius * radius; } }
The @Override annotation is optional but recommended. It tells the compiler that you intend to override a method, and it will produce an error if the method does not actually override anything from a superclass. This catches typos and signature mismatches early.
Static methods behave differently. A static method in a subclass with the same signature as a static method in the parent does not override it; it hides it. Hiding is resolved at compile time based on the reference type, not at runtime. This means calling a hidden static method through a parent reference executes the parent's version, even if the object is an instance of the subclass.
public class Parent { public static void greet() { System.out.println("Hello from Parent"); } } public class Child extends Parent { public static void greet() { System.out.println("Hello from Child"); } } Parent p = new Child(); p.greet(); // prints "Hello from Parent"
Overriding is central to polymorphism. When you call an overridden method through a parent reference, the JVM dispatches to the subclass's implementation at runtime. This is the basis of the @Override mechanism and the behavior of virtual methods in Java.
Access Modifiers and Their Effect on Inherited Members
The four access levels directly determine what a subclass can inherit and how it can use those members. public members are inherited everywhere. protected members are inherited in any subclass, even across packages, and are also accessible within the same package. Package-private (no modifier) members are inherited only if the subclass is in the same package. private members are never inherited, but a subclass can still access them indirectly through public or protected methods defined in the parent.
| Modifier | Same Package | Subclass in Different Package | Non-subclass Anywhere |
|---|---|---|---|
public | Yes | Yes | Yes |
protected | Yes | Yes | No |
| (default) | Yes | No | No |
private | No | No | No |
This table is a quick reference, but the real behavior matters when you design a class hierarchy. For example, making a field protected exposes it to all subclasses, which can lead to tight coupling. A better approach is often to keep fields private and provide protected getters and setters if subclasses need controlled access. This preserves encapsulation while still allowing inheritance.
Constructors Are Not Inherited
Constructors are not inherited in Java. A subclass does not automatically get the parent's constructors. Instead, every subclass constructor must call a parent constructor, either explicitly with super(...) or implicitly if the parent has a no-argument constructor. If the parent does not have a no-argument constructor, the subclass must explicitly call a matching super constructor as the first statement.
public class Vehicle { private String model; public Vehicle(String model) { this.model = model; } } public class Car extends Vehicle { private int doors; public Car(String model, int doors) { super(model); // must call parent constructor this.doors = doors; } }
If you omit super(model), the compiler will attempt to call super() and fail because Vehicle has no no-argument constructor. This is a common compile-time error. The rule is: if the parent defines any constructor, the subclass must explicitly call one of them.
Using super to Access Inherited Members
The super keyword serves two purposes: it calls a parent constructor, and it accesses parent members from within a subclass. When you override a method, you can call the parent implementation using super.methodName(). This is useful when you want to extend behavior rather to replace it entirely.
public class Base { public void log(String message) { System.out.println("Base: " + message); } } public class Derived extends Base { @Override public void log(String message) { super.log(message); // call parent first System.out.println("Derived: " + message); } }
Calling super is also the way to access a shadowed field, as shown earlier. Without super, you would refer to the subclass's field. The super reference is not a separate object; it is a syntactic way to access the parent's members from within the subclass's context.
Common Mistakes with Inherited Members
One frequent mistake is assuming that private members are inherited. They are not, and attempting to access them directly causes a compile error. Another mistake is overriding a method and accidentally changing the parameter list, which creates an overload instead of an override. The @Override annotation catches this immediately. A third mistake is calling an overridden method from a constructor. Because the subclass fields are not initialized yet, the method may run with null or default values, leading to runtime errors.
public class Parent { public Parent() { init(); // calls overridden method in subclass } protected void init() { System.out.println("Parent init"); } } public class Child extends Parent { private String data = "hello"; @Override protected void init() { System.out.println(data.length()); // NullPointerException } }
nIn this example, the parent constructor invokes init(), which is overridden in Child. At that point, data is still null because the subclass field initializer has not run yet. This is a classic pitfall. Avoid calling overridable methods from constructors; if you must, design the method to tolerate uninitialized state.
Performance and Maintainability of Inherited Members
Inheritance has a negligible runtime cost in modern JVMs. Virtual method dispatch is optimized through inline caching, so the overhead of an overridden method call is small. The larger cost is maintainability. Deep inheritance hierarchies make it hard to trace where a member is defined and which implementation runs. A method inherited from a grandparent may be overridden in a subclass, and understanding the effective behavior requires checking every level.
When you design a class hierarchy, favor composition over inheritance when the relationship is not a clear "is-a" relationship. Inherited members create a contract: subclasses inherit not only behavior but also the obligation to maintain that behavior. If a parent method changes, all subclasses are affected. This coupling is sometimes necessary, but it should be deliberate.
A practical rule is to keep inheritance shallow and to use final on methods that should not be overridden. Marking a method final prevents subclasses from changing its behavior, which can be useful for security or invariant preservation. Similarly, making a class final stops inheritance entirely. These tools give you control over how your members are used.
For performance-sensitive code, the JVM can inline final and private methods more aggressively because they are not subject to dynamic dispatch. However, you should not optimize prematurely. Write clear, correct code first, and use profiling to identify real bottlenecks. The cost of a virtual call is rarely the limiting factor in a well-designed application.