Java extends Keyword: Inheritance Syntax and Behavior
java extends keyword: Explains the Java extends keyword: inheritance syntax, constructor chaining, method overriding, and when to prefer composition.
The java extends keyword declares an inheritance relationship between two classes. When class B extends class A, B inherits A's accessible fields and methods, and instances of B can be used wherever A is expected. This is the foundation of Java's class-based inheritance, and getting the details right matters because the compiler enforces rules about access, constructors, and method dispatch that are easy to overlook.
Basic Syntax of extends
public class Vehicle { protected String registrationNumber; public void startEngine() { System.out.println("Engine started"); } } public class Car extends Vehicle { private int numberOfDoors; public void openTrunk() { System.out.println("Trunk opened"); } }
The extends clause appears after the class name and before the opening brace. A class can extend exactly one other class; Java does not support multiple inheritance of classes. The subclass Car now has access to registrationNumber (because it is protected) and startEngine() (because it is public), in addition to its own numberOfDoors field and openTrunk() method.
What a Subclass Actually Inherits
Inheritance is not a copy operation. The subclass does not receive private members, and it does not receive constructors. What it does receive depends on access modifiers:
| Member access | Inherited by subclass | Notes |
|---|---|---|
public | Yes | Accessible everywhere |
protected | Yes | Accessible in subclass and same package |
| package-private (no modifier) | Yes, if same package | Not accessible if subclass is in another package |
private | No | Not inherited at all |
A common misunderstanding is that private fields are "inherited but hidden." In Java, private members of the superclass are simply not part of the subclass's accessible surface. If the superclass exposes a public or protected getter, the subclass can use that getter, but it cannot reference the private field directly.
Static methods are also inherited in the sense that a subclass can call them without qualification, but they are not subject to overriding. If a subclass declares a static method with the same signature, that is method hiding, not overriding, and the version that runs depends on the reference type, not the runtime type.
Constructor Chaining and the super Keyword
Constructors are not inherited. Every constructor of a subclass must call a constructor of the superclass, either explicitly with super(...) or implicitly through the default no-argument constructor.
public class Car extends Vehicle { private int numberOfDoors; public Car(String registrationNumber, int numberOfDoors) { super(registrationNumber); // explicit call to Vehicle(String) this.numberOfDoors = numberOfDoors; } }
If the superclass has no no-argument constructor, the subclass must call super(...) explicitly as the first statement in each constructor. The compiler will reject the subclass with a "constructor cannot be applied to given types" error otherwise. This is a frequent source of compile errors for developers new to inheritance.
The super keyword also gives access to overridden methods. Inside a subclass method, super.methodName() invokes the superclass version, which is useful when the subclass extends the behavior of the parent rather than replacing it entirely.
Method Overriding and Runtime Dispatch
When a subclass declares a method with the same signature and return type as a method in the superclass, it overrides that method. The @Override annotation is optional but recommended; the compiler will report an error if the annotated method does not actually override anything, which catches typos in the signature.
public class Car extends Vehicle { @Override public void startEngine() { System.out.println("Car engine started"); super.startEngine(); // optional: run the superclass logic too } }
Overriding affects runtime dispatch. Consider this code:
Vehicle vehicle = new Car(); vehicle.startEngine(); // calls Car.startEngine(), not Vehicle.startEngine()
The decision about which method runs is made at runtime based on the actual object type, not the declared reference type. This is the mechanism behind polymorphism in Java. The extends keyword is what makes this possible: without the inheritance relationship, Vehicle could not reference a Car instance at all.
There are rules that constrain overriding. The overriding method cannot reduce visibility (a public method cannot be overridden as protected), and it cannot throw broader checked exceptions than the original method. These rules exist so that code written against the superclass type continues to behave correctly when a subclass instance is substituted.
extends vs implements: Choosing the Right Relationship
Java distinguishes class inheritance from interface implementation. A class uses extends to inherit from another class and implements to satisfy an interface contract.
public class ElectricCar extends Car implements Chargeable { @Override public void charge() { // implementation required by Chargeable } }
The practical difference is that a class can implement multiple interfaces but extend only one class. When you need to share implementation code, extends is the tool. When you only need to guarantee a set of methods, implements is usually the better choice because it does not tie the class into a single inheritance chain.
A common design decision is whether to use an abstract superclass or an interface. An abstract class with extends is appropriate when the subclasses share state or non-abstract behavior. An interface is appropriate when the contract is purely behavioral and implementations may come from unrelated hierarchies.
Common Mistakes and Their Runtime Consequences
The most common mistakes with extends surface as compile errors or subtle runtime behavior.
Trying to extend a final class fails at compile time. The final modifier on a class is an explicit statement that the class is not designed for inheritance. The compiler enforces this, so there is no workaround without removing the modifier.
Attempting to override a final method also fails. A final method is part of the superclass's contract and cannot be changed by subclasses.
A subtler issue is overriding a method and accidentally calling the wrong version. If a subclass overrides startEngine() and the superclass constructor calls startEngine() internally, the subclass version runs before the subclass constructor has completed. This can lead to null fields or partially initialized objects. The safe pattern is to avoid calling overridable methods from constructors.
Another frequent mistake is assuming that a subclass can narrow the type of a field. Fields are hidden, not overridden. If both the superclass and subclass declare a field with the same name, the subclass has two distinct fields, and the one that is accessed depends on the reference type. This is almost always a design error.
Maintainability and Design Tradeoffs
Deep inheritance hierarchies are difficult to maintain because behavior is spread across many levels and changes to a superclass can affect every subclass. A change to a protected field or method in a base class can break subclasses that were written years earlier and in different packages.
Composition is often a better alternative. Instead of extending a class to reuse behavior, hold an instance of the class as a field and delegate to it. This avoids the coupling that extends introduces and makes dependencies explicit.
public class Car { private final Engine engine; public Car(Engine engine) { this.engine = engine; } public void startEngine() { engine.start(); } }
The decision between extends and composition depends on whether the relationship is genuinely an "is-a" relationship. A Car is a Vehicle, so inheritance is defensible. A Car has an Engine, so composition is the natural fit there. When the relationship is "has-a," using extends creates artificial coupling and makes the code harder to test, because the subclass cannot be instantiated without the superclass's constructor requirements.
The extends keyword is not inherently problematic. The problems come from using it where the relationship does not actually exist, or from building hierarchies so deep that the behavior of any single class cannot be understood without reading every ancestor. Keeping hierarchies shallow and preferring composition for "has-a" relationships keeps the code maintainable while still allowing inheritance where it genuinely applies.