PrepZone Logo
PrepZone

Inheritance and Composition

extends, super and this — plus the judgement call about when not to inherit at all.

Read these first

Why this matters

  • Inheritance is the most over-used feature in object-oriented code, and the resulting hierarchies are the hardest thing to refactor later.
  • "Favour composition over inheritance" is advice everyone repeats and few can justify. The justification is specific and worth knowing.
  • Java's single-inheritance rule forces a design decision early, so understanding the trade-off is not optional.

How inheritance works

Java
public class Vehicle {
    protected final String registration;
    private int odometer;

    public Vehicle(String registration) {
        this.registration = registration;
    }

    public void drive(int kilometres) {
        odometer += kilometres;
    }

    public String describe() {
        return "Vehicle " + registration;
    }
}

public class Car extends Vehicle {
    private final int seats;

    public Car(String registration, int seats) {
        super(registration);              // must come first
        this.seats = seats;
    }

    @Override
    public String describe() {
        return super.describe() + " with " + seats + " seats";
    }
}

What a subclass gets, and what it does not

  • It inherits all public and protected members, plus package-private ones if it is in the same package.
  • It does not inherit private members. They exist in the object but are unreachable by name.
  • It does not inherit constructors. It must call one of the parent's via super(...).
  • super.method() calls the parent's version, which is how you extend behaviour rather than replace it.
  • Every class without an explicit extends silently extends Object.
Paymentabstract settle()
CardPaymentsettle() → gateway
UpiPaymentsettle() → UPI handle
WalletPaymentsettle() → balance
Each subclass keeps the Payment contract but supplies its own settle() body. Calling settle() on a Payment variable runs the subclass version.

Single inheritance, and the reason for it

A Java class may extend exactly one class but implement any number of interfaces.

Java
class Hybrid extends Car implements Electric, Serviceable { }   // legal
// class Broken extends Car, Truck { }                          // not legal

The reason is the diamond problem. If a class inherited concrete state and behaviour from two parents that both defined start(), there would be no principled way to decide which runs. Interfaces avoid this because, historically, they carried no implementation at all.

Composition

Composition means holding another object as a field and delegating to it.

Java
public class Engine {
    void start() { }
    void stop() { }
}

public class Car {
    private final Engine engine = new Engine();     // has-a

    public void startUp() {
        engine.start();                              // delegation
    }
}
Inheritance — is a
OrderServiceextends EmailSender
EmailSenderCannot be swapped
Composition — has a
OrderServiceholds a Notifier
EmailNotifier
SmsNotifier
Inheritance locks you into one parent for the object's whole life. Composition lets you swap the collaborator, which is what makes testing and change cheap.

The difference from inheritance is that Car chooses exactly which parts of Engine to expose. It is not forced to inherit the whole surface.

Why composition usually wins

AspectInheritanceComposition
Relationshipis-ahas-a
CouplingTight — subclass depends on parent internalsLoose — only on the part's public API
Fixed atCompile timeRun time, by swapping the field
How manyOne superclassAs many parts as you like
Exposed surfaceEverything public in the parentOnly what you choose to delegate
Parent changesMay silently break subclassesContained behind the delegation
  • Relationship

    Inheritanceis-a
    Compositionhas-a
  • Coupling

    InheritanceTight — subclass depends on parent internals
    CompositionLoose — only on the part's public API
  • Fixed at

    InheritanceCompile time
    CompositionRun time, by swapping the field
  • How many

    InheritanceOne superclass
    CompositionAs many parts as you like
  • Exposed surface

    InheritanceEverything public in the parent
    CompositionOnly what you choose to delegate
  • Parent changes

    InheritanceMay silently break subclasses
    CompositionContained behind the delegation

Composition trades a little extra typing for the ability to change your mind later.

The deepest problem with inheritance is the fragile base class. A subclass can depend on how the parent is implemented, not just what it promises, and an innocent-looking change to the parent then breaks it.

Java
// A counting set, by inheritance — and it is wrong
public class CountingSet<E> extends HashSet<E> {
    private int added;

    @Override
    public boolean add(E element) {
        added++;
        return super.add(element);
    }

    @Override
    public boolean addAll(Collection<? extends E> elements) {
        added += elements.size();
        return super.addAll(elements);     // which internally calls add() for each
    }

    public int added() { return added; }
}
Java
CountingSet<String> set = new CountingSet<>();
set.addAll(List.of("a", "b", "c"));
System.out.println(set.added());      // 6, not 3

HashSet.addAll is implemented by calling add repeatedly. The subclass counted each element twice, once in its own addAll and once in its own add. Nothing in the documented contract told you that, and a future JDK could change it either way.

The composed version has no such problem:

Java
public class CountingSet<E> {
    private final Set<E> delegate = new HashSet<>();
    private int added;

    public boolean add(E element) {
        added++;
        return delegate.add(element);
    }

    public boolean addAll(Collection<? extends E> elements) {
        added += elements.size();
        return delegate.addAll(elements);   // delegate's internals cannot call back in
    }

    public int added() { return added; }
}

When inheritance is the right answer

It is not always wrong. Reach for it when all of these hold.

  • The is-a relationship is genuinely true, not merely convenient for reuse.
  • The subclass can be used anywhere the parent is expected without surprising anyone — the substitution principle.
  • The parent was designed for extension, with documented extension points and a stable contract.
  • You control both classes, or the parent explicitly supports subclassing.

this and super

  • this refers to the current object. this.field = field disambiguates a field from a parameter of the same name.
  • this(...) calls another constructor in the same class and must be the first statement.
  • super accesses the parent's members, which is the only way to reach an overridden method.
  • super(...) calls a parent constructor, must be first, and is inserted automatically as super() if you omit it.

Common misreadings

  • "Inheritance means less code, so it is better." It means less typing now and more coupling forever. Count the cost on both sides.
  • "protected fields are good for subclasses." They make the field part of your public contract for anyone who extends you. Prefer protected methods over protected fields.
  • "Java has no multiple inheritance." It has no multiple inheritance of state. Multiple inheritance of type, and since Java 8 of default behaviour, both exist.
  • "A subclass inherits private fields." The memory is there, but the names are not accessible. Only a parent method can reach them.
  • "Deep hierarchies show good design." Depth beyond two or three levels is usually a sign that composition was the right tool.

Quick recall

Everything you need if you only revisit this box.

  • is-a → inheritance. has-a → composition. When unsure, compose.
  • A subclass inherits accessible members but never constructors, and super(...) must be the first statement.
  • Java allows one superclass, many interfaces, avoiding the diamond problem for state.
  • Inheritance is compile-time and tight; composition is run-time and loose, and exposes only what you delegate.
  • The fragile base class problem: subclasses can depend on parent internals, as the CountingSet double-count shows.
  • Use inheritance only when is-a is true, substitution holds, and the parent was designed for extension. Otherwise mark the class final.

Test yourself

Answer these before moving on — recall is what makes it stick.