Composition vs inheritance; extends & super
◈ 5 cardshas-a vs is-a, base-class init via super(...), delegation, and when to prefer composition.
Two ways to reuse a class
There are two ways to build a new class out of an existing one:
- Composition (has-a): the new class holds a reference to the existing one as a field and forwards (delegates) work to it. A
Carhas anEngine. - Inheritance (is-a): the new class
extendsthe existing one and becomes a kind of it, inheriting its members. ATruckis aVehicle.
Java is single-inheritance: a class may extend exactly one superclass. Every class that does not name a superclass implicitly extends Object.
Worked example: compose an Engine, extend a Vehicle
class Engine {
void start() { System.out.println("Engine started"); }
}
class Car { // has-a: Car COMPOSES Engine
private final Engine engine = new Engine();
void drive() {
engine.start(); // delegate to the held object
System.out.println("Driving");
}
}
Car does not expose Engine's methods; it chooses what to forward. You can swap the engine implementation later without changing Car's public surface. This loose coupling is why composition is the default choice.
super(...) drives base-class construction
With inheritance, the subclass must initialize the part of itself that belongs to the superclass. That happens through a super(...) call:
class Vehicle {
private final int wheels;
Vehicle(int wheels) { this.wheels = wheels; }
int getWheels() { return wheels; }
}
class Truck extends Vehicle { // is-a: Truck IS A Vehicle
private final double tonnage;
Truck(double tonnage) {
super(6); // MUST be the first statement
this.tonnage = tonnage;
}
}
super(6) runs Vehicle's constructor before Truck initializes its own fields. It must be the first statement in the constructor. If you omit it, Java inserts an implicit super() (the no-arg superclass constructor) for you — and if the superclass has no no-arg constructor, that implicit call fails to compile. That is one of the most common surprises in inheritance.
When to prefer which
Prefer composition unless the subtype genuinely is a specialized form of the supertype and can stand in for it everywhere (the Liskov substitution test). Inheriting only to reuse a few methods couples you to the superclass's entire interface and its future changes — a fragile bargain.