Inheritance
Inheritance lets one class acquire the members of another through extends, forming an is-a relationship — the mechanism behind Java's class hierarchies, and one that must be chosen deliberately over composition.
Inheritance lets a class acquire the fields and methods of another class using the extends keyword, establishing an "is-a" relationship between them. The class being inherited from is the superclass (or base class); the class doing the inheriting is the subclass (or derived class). Inheritance is one of Java's core mechanisms for code reuse and building type hierarchies.
extends, Base Class, and Subclass
A subclass declares its superclass with extends. It then automatically gains access to the superclass's non-private members, according to their access modifiers, and can add its own new fields and methods on top.
Dog did not redeclare name or eat() — it inherited them from Animal simply by extending it, and added bark() as new behavior.
public class Animal { // superclass / base class
protected String name;
public void eat() {
System.out.println(name + " is eating");
}
}
public class Dog extends Animal { // subclass / derived class
public void bark() {
System.out.println(name + " says woof"); // inherited field
}
}
Dog d = new Dog();
d.name = "Rex";
d.eat(); // inherited method
d.bark(); // Dog's own methodInherited Members
A subclass inherits all non-private fields and methods of its superclass (subject to access modifiers — package-private members are only inherited usefully within the same package). private members exist in the object but are not directly accessible by name from the subclass; constructors are not inherited at all — a subclass must define its own, though it can chain to a superclass constructor.
vin still exists inside every Car object (it is part of the inherited Vehicle state), but Car code can only reach it through the public getVin() method, not by naming vin directly.
class Vehicle {
protected int wheels = 4;
private String vin = "unknown"; // exists, but not directly reachable by name in subclasses
public String getVin() { return vin; } // the controlled path to reach it
}
class Car extends Vehicle {
void describe() {
System.out.println("wheels: " + wheels); // OK, inherited protected field
System.out.println("vin: " + getVin()); // OK, via inherited public method
// System.out.println(vin); // compile error: private, not accessible by name here
}
}super — Accessing the Superclass
super refers to the superclass part of the current object. It is used to call an overridden superclass method explicitly, or to access a superclass field that is shadowed by a field of the same name in the subclass.
super.describe() explicitly invokes Shape's version of the method from inside the overriding method, rather than recursing back into Square's own describe().
class Shape {
void describe() {
System.out.println("a shape");
}
}
class Square extends Shape {
@Override
void describe() {
super.describe(); // call the superclass version first
System.out.println("specifically, a square");
}
}
new Square().describe();
// Output:
// a shape
// specifically, a squareConstructor Chaining with super()
Because constructors are not inherited, a subclass constructor must ensure the superclass portion of the object is initialized — either implicitly (the compiler inserts a no-argument super() call automatically) or explicitly via a super(...) call, which must be the first statement in the subclass constructor.
Because Person has no no-argument constructor, Student is required to call super(name) explicitly — the compiler cannot insert an implicit super() that doesn't exist.
If a subclass constructor has no explicit this(...) or super(...) call, the compiler inserts an implicit super() as the first statement — this only compiles if the superclass has an accessible no-argument constructor.
class Person {
protected String name;
public Person(String name) {
this.name = name;
}
}
class Student extends Person {
private String school;
public Student(String name, String school) {
super(name); // must be first statement; calls Person(String)
this.school = school;
}
}Method Inheritance
A subclass automatically has every non-private method of its superclass available to call, exactly as if it had been declared in the subclass itself, unless the subclass chooses to override it (covered in depth in the Method Overriding topic) or the method is hidden by a static method with the same signature.
SalesPrinter never declares printHeader() itself — it simply uses the one it inherited from Printer.
class Printer {
void printHeader() {
System.out.println("=== Report ===");
}
}
class SalesPrinter extends Printer {
void printSalesData() {
printHeader(); // inherited, called with no qualification needed
System.out.println("Sales: $1200");
}
}Java's Single Class Inheritance
A Java class may extend exactly one superclass (Object, implicitly, if none is named) — Java does not support multiple class inheritance. A class can, however, implement multiple interfaces, which is the language's answer to needing behavior from more than one source (see the Interfaces topic).
Extending is limited to a single class specifically to avoid the ambiguity that arises when two superclasses define conflicting state or behavior — interfaces avoid this by (traditionally) not carrying instance state.
class A { }
class B { }
// class C extends A, B { } // compile error: Java has no multiple class inheritance
class C extends A implements Runnable, Comparable<C> { // OK: one class, multiple interfaces
public void run() { }
public int compareTo(C other) { return 0; }
}Composition versus Inheritance
Composition means a class holds a reference to another object and delegates to it, rather than extending it — a "has-a" relationship instead of "is-a". Inheritance is appropriate when a genuine is-a relationship holds and the subclass should be substitutable wherever the superclass is expected; composition is often preferred when a class merely needs to reuse another class's functionality without being a true subtype of it, because composition avoids tightly coupling the two classes' internal implementations.
Car2 reuses Engine's behavior through delegation rather than inheritance — Car2 is not a kind of Engine, it merely uses one internally.
A common design guideline is "favor composition over inheritance" for code reuse specifically — reserve inheritance for cases where the is-a relationship, and substitutability, genuinely holds.
// Inheritance: a Car genuinely IS-A Vehicle
class Vehicle { void move() { } }
class Car extends Vehicle { }
// Composition: a Car HAS-A Engine, but is not itself an Engine
class Engine { void start() { } }
class Car2 {
private final Engine engine = new Engine(); // composition
void start() { engine.start(); } // delegation
}Practical Usage
Inheritance is common for modeling genuine type hierarchies: an Exception hierarchy, a Shape hierarchy with Circle/Square/Triangle, or framework base classes designed specifically to be extended (with protected hook methods for subclasses to customize). It is less appropriate purely as a shortcut to avoid retyping a few methods when no real is-a relationship exists.
Complete Example
Manager reuses Employee's fields and constructor logic via super(...), and its computePay() builds on the inherited base computation via super.computePay() rather than duplicating it.
class Employee {
protected String name;
protected double baseSalary;
public Employee(String name, double baseSalary) {
this.name = name;
this.baseSalary = baseSalary;
}
public double computePay() {
return baseSalary;
}
@Override
public String toString() {
return name + ": $" + computePay();
}
}
class Manager extends Employee {
private double bonus;
public Manager(String name, double baseSalary, double bonus) {
super(name, baseSalary);
this.bonus = bonus;
}
@Override
public double computePay() {
return super.computePay() + bonus;
}
}
public class Main {
public static void main(String[] args) {
Employee e = new Employee("Sam", 50000);
Manager m = new Manager("Ada", 70000, 15000);
System.out.println(e);
System.out.println(m);
}
}Mental Model
Picture a subclass object as physically containing a "superclass-shaped" slice inside it — every Manager object has a full Employee's worth of state and behavior built in, plus its own additions. super() and super.method() reach into that inherited slice explicitly, while ordinary calls use whichever version (inherited or overridden) applies.
Quick Reference
Concept Syntax / rule
---------------------------- --------------------------------------------------
Declare inheritance class Sub extends Super { }
Call superclass constructor super(args...); -- must be first statement
Call overridden superclass super.methodName(args...)
method
Access superclass field super.fieldName (needed if shadowed by a subclass field)
Multiple class inheritance NOT supported -- one extends only; use interfaces instead
Constructors NOT inherited -- subclass must define its ownKey Takeaways
extends establishes an is-a relationship: a subclass inherits its superclass's non-private members.
super() calls a superclass constructor and must be the first statement in a subclass constructor.
super.method() explicitly invokes the superclass's version of a method from inside an override.
Java supports single class inheritance only — a class extends at most one superclass, but can implement multiple interfaces.
Composition (has-a, via delegation) is often preferable to inheritance when there is no genuine is-a relationship.
class Bird { } class Fish { } class FlyingFish extends Bird, Fish { } // compile error: multiple class inheritance interface CanFly { void fly(); } interface CanSwim { void swim(); } class FlyingFish implements CanFly, CanSwim { public void fly() { } public void swim() { } }
Java classes cannot extend more than one class; when behavior from multiple sources is needed, implement multiple interfaces instead.
class Base { Base(String s) { } } class Derived extends Base { Derived() { // no super(...) call, and Base has no no-argument constructor } // compile error } class Base { Base(String s) { } } class Derived extends Base { Derived() { super("default"); } }
Without an explicit super(...) call, the compiler inserts an implicit no-argument super() — which fails to compile if the superclass has no accessible no-argument constructor.
class Bird { void fly() { System.out.println("flying"); } } class Penguin extends Bird { // Penguin IS-A Bird in taxonomy, but cannot actually fly -- // inheriting fly() here forces an awkward override or a broken contract } interface Bird { } interface FlyingBird extends Bird { void fly(); } class Penguin implements Bird { } // no fly() forced on it class Eagle implements FlyingBird { public void fly() { System.out.println("flying"); } }
Forcing inheritance onto a relationship that is not truly substitutable (a Penguin cannot fulfill "can fly") leads to broken behavior; modeling the capability separately avoids the mismatch.
class Stack extends ArrayList<Integer> { // inherits add(int index, E), remove(int index), etc. -- callers can now // bypass push/pop and corrupt "stack" ordering directly via the list API } class Stack { private final List<Integer> data = new ArrayList<>(); // composition void push(int v) { data.add(v); } int pop() { return data.remove(data.size() - 1); } }
Extending ArrayList to "reuse" its storage exposes its entire public API, including operations that violate stack discipline; composing with a List internally and exposing only push/pop keeps the abstraction honest.
What relationship does extends establish between a subclass and its superclass?
An "is-a" relationship: the subclass is considered a specific kind of the superclass, and inherits its non-private members.
Are constructors inherited by a subclass?
No. A subclass must define its own constructors; it can chain to a superclass constructor with super(...), but it does not automatically receive the superclass's constructors as its own.
Why must a super(...) call be the first statement in a subclass constructor?
The superclass portion of the object must be fully initialized before any of the subclass's own initialization runs, so the language requires the call establishing that (explicit super(...) or an implicit one) to happen before anything else in the constructor body.
Can a Java class extend more than one class?
No — Java supports only single class inheritance. A class can, however, implement multiple interfaces, which is how the language provides behavior from multiple sources without the ambiguity multiple class inheritance would introduce.
What is the difference between inheritance and composition?
Inheritance (is-a) has a subclass acquire a superclass's members directly through extends. Composition (has-a) has a class hold a reference to another object and delegate to it. Composition is often preferred for code reuse when no genuine is-a/substitutability relationship exists.
If a superclass field is shadowed by a same-named field in a subclass, how does the subclass access the superclass's version?
Via super.fieldName, which explicitly reaches into the inherited superclass slice of the object rather than the subclass's own shadowing field.
Interview-style: why does Java restrict class inheritance to a single superclass?
Multiple class inheritance introduces ambiguity when two superclasses define conflicting state or method implementations (the "diamond problem"). Java sidesteps this by allowing only one superclass while still permitting multiple interfaces, which historically carried no instance state and so could not conflict the same way.
Write a Shape base class with a protected String name and a method describe(), and a Circle subclass that adds a radius field, chains to the base constructor, and prints radius in its own describe() after calling the base version.
class Shape { protected String name; public Shape(String name) { this.name = name; } public void describe() { System.out.println("Shape: " + name); } } class Circle extends Shape { private final double radius; public Circle(String name, double radius) { super(name); this.radius = radius; } @Override public void describe() { super.describe(); System.out.println("radius: " + radius); } }
Circle's constructor chains to Shape's via super(name) as its first statement, and describe() calls super.describe() before adding its own radius line, reusing rather than duplicating the base behavior.
What is printed, and in what order?
Prints "A()", then "B()", then "C()". Each constructor implicitly calls super() as its first statement, so construction always proceeds from the top of the hierarchy downward before the requested subclass's own constructor body finishes.
Write a LoggingList class that composes (not extends) a List<String> internally, and adds a log to a separate list every time an item is added, without exposing the full List API.
import java.util.ArrayList; import java.util.List; public class LoggingList { private final List<String> items = new ArrayList<>(); private final List<String> log = new ArrayList<>(); public void add(String item) { items.add(item); log.add("added: " + item); } public List<String> getItems() { return List.copyOf(items); } public List<String> getLog() { return List.copyOf(log); } }
Using composition instead of extending ArrayList<String> means LoggingList exposes only the narrow API it chooses (add, getItems, getLog), so every addition is guaranteed to go through the logging logic — extending ArrayList would let callers bypass it via inherited methods like addAll.
What does this print, and does Truck still have access to Vehicle's horn() behavior?
Prints "HONK" — Truck's own horn() runs. Vehicle's version is still reachable from inside Truck via super.horn(), but nothing in this code calls it, so only the overriding behavior executes here.
class A {
A() { System.out.println("A()"); }
}
class B extends A {
B() { System.out.println("B()"); }
}
class C extends B {
C() { System.out.println("C()"); }
}
public class Main {
public static void main(String[] args) {
new C();
}
}class Vehicle {
void horn() { System.out.println("beep"); }
}
class Truck extends Vehicle {
void horn() { System.out.println("HONK"); } // no @Override, no "super"
}
new Truck().horn();