Java Single Inheritance Explained
java single inheritance: Learn how Java single inheritance works: extending one class, calling super constructors, overriding methods, and understanding its role in po...
java single inheritance requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
Java enforces single inheritance for classes, meaning a class can directly extend only one superclass. This is a deliberate language design choice that avoids the complexity and ambiguity of multiple inheritance of implementation. While you can implement multiple interfaces, class-level inheritance in Java stays single. Understanding this rule is fundamental to designing object hierarchies that remain predictable and maintainable.
The Core Rule: One Superclass Only
The extends keyword establishes an inheritance relationship between two classes. A subclass inherits accessible fields and methods from its superclass, and it can add new members or override existing ones. The single inheritance rule means this chain is strictly linear: each class has exactly one direct parent.
class Animal { void eat() { System.out.println("Animal eats"); } } class Dog extends Animal { void bark() { System.out.println("Dog barks"); } }
Here, Dog inherits eat() from Animal. If you try to declare a class that extends two classes, the compiler rejects it:
// This does not compile class FlyingDog extends Animal, Bird { }
The reasoning behind this restriction is the diamond problem, where ambiguity arises if two parent classes define the same method. Java sidesteps this by permitting only single inheritance for classes, while interfaces–which are abstract contracts–can be implemented freely.
The super Keyword: Accessing the Parent
A subclass can interact with its superclass using super. There are two primary uses: calling a superclass constructor and invoking a superclass method that has been overridden.
Constructor Chaining
When you create a subclass instance, the superclass constructor is called implicitly if you do not explicitly call it. This ensures that the superclass is fully initialized first. If the superclass has only parameterized constructors, the subclass must call one explicitly via super(...).
class Vehicle { private String engine; Vehicle(String engine) { this.engine = engine; } } class Car extends Vehicle { Car(String engine) { super(engine); // mandatory because Vehicle has no no-arg constructor } }
Failing to call super(engine) here results in a compile-time error because Vehicle has no default constructor. This rule forces the subclass to participate in the proper initialization of the inherited state.
Method Invocation
Within an overriding method, super.methodName() lets you call the parent's version. This is useful for extending behavior rather than completely replacing it.
class BasePrinter { void printMessage() { System.out.println("Base message"); } } class DecoratedPrinter extends BasePrinter { @Override void printMessage() { System.out.println("Before base"); super.printMessage(); System.out.println("After base"); } }
This pattern allows a subclass to add pre- and post-processing while reusing the parent's logic, which keeps the code DRY and avoids duplication.
Overriding Methods and Method Dispatch
Single inheritance creates a linear chain where method resolution is straightforward: the subclass's override wins, and if none exists, the superclass method is used. Polymorphism leverages this: a reference of the superclass type can point to a subclass object, and the correct overridden method is called at runtime.
class Shape { void draw() { System.out.println("Drawing shape"); } } class Circle extends Shape { @Override void draw() { System.out.println("Drawing circle"); } } public class Main { public static void main(String[] args) { Shape s = new Circle(); s.draw(); // Outputs: Drawing circle } }
Because the inheritance chain is single, there is no ambiguity about which overridden method to invoke. The runtime can traverse a single path up the class hierarchy to find the most specific implementation.
Constructors and Initialization Order
When a subclass object is created, the superclass constructor runs first, from the topmost superclass down to the subclass. This ensures that inherited fields are initialized before any subclass-specific logic runs.
class Parent { Parent() { System.out.println("Parent constructor"); } } class Child extends Parent { Child() { System.out.println("Child constructor"); } } public class Demo { public static void main(String[] args) { new Child(); // Outputs: Parent constructor, then Child constructor } }
An important performance and correctness consideration: because of this initialization order, you must avoid calling overridable methods from a constructor, as the subclass's overridden version might execute before the subclass's fields are initialized, leading to subtle bugs.
Designing with Single Inheritance in Mind
Single inheritance encourages deep but narrow hierarchies. When designing a class hierarchy, you should favor composition over inheritance when the relationship is not a clear "is-a" relationship. Inheritance should be used only when a subclass truly is a more specific version of the superclass, and it must preserve the superclass's contract.
For example, if you want to reuse logging functionality across different classes, a Logger class used as a field is often more flexible than making every class inherit from a LoggingBase class. Composition avoids the coupling that inheritance introduces and allows additional flexibility without being constrained by single inheritance.
Common Mistakes and How to Avoid Them
One common mistake is attempting to change the return type of an overridden method. Java allows covariant return types, meaning the overriding method can return a subtype of the original return type, but it cannot return an unrelated type. This restriction is tied to the inheritance chain and keeps the subclass compatible with the superclass contract.
Another frequent error is hiding static methods. If a subclass declares a static method with the same signature as a static method in the superclass, that is not overriding but hiding. The decision about which method is called depends on the reference type, not the object type, which can lead to surprising and error-prone behavior. It is generally better to call static methods via the class name to avoid ambiguity.
Single Inheritance vs. Interfaces
The single inheritance rule applies to classes, but a class can implement any number of interfaces. This distinction is significant: interfaces provide a contract for behavior without implementation, whereas a superclass may carry state and concrete methods. Use inheritance when you want code reuse and shared implementation; use interfaces when you need to define capabilities that unrelated classes can implement.
| Aspect | Single Inheritance | Multiple Interfaces |
|---|---|---|
| Implementation reuse | Yes | No (until default methods) |
| State inheritance | Fields inherited | No fields (Java 8+) |
| Diamond problem | Not possible for classes | Resolved with rules |
| Flexibility | Limited to one parent | Many contracts |
In practice, combining a single superclass with multiple interfaces is a common and effective pattern. For instance, a class can extend an abstract base that provides common fields and methods, while implementing several interfaces that define its external capabilities.
The Role of Access Modifiers
Access modifiers (private, public, protected) control which members are inherited and visible. Private members are not inherited, but they still exist in the object's memory, and they can be accessed indirectly through public or protected methods. Protected members are inherited and accessible in subclasses, but not from unrelated classes in a different package. When designing a class intended as a superclass, plan the visibility of members carefully: fields should usually be private and exposed through protected or public accessors, to maintain encapsulation and avoid breaking subclasses when the internal representation changes.