Composition vs inheritance; extends & super
has-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 Car has an Engine.
- Inheritance (is-a): the new class extends the existing one and *becomes a kind of* it, inheriting its members. A Truck is a Vehicle.
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.