Inheritance vs Composition in Java
Object reuse looks simple: extend a class and gain behavior. In production systems, inheritance creates long term risk unless design supports extension intentionally.
Favor Composition Over Inheritance
Inheritance creates an is-a relationship. Composition creates a has-a relationship.
Inheritance couples behavior to internal implementation. Composition delegates behavior through a field.
Problem pattern:
class RiskList extends ArrayList<Trade> {
@Override
public boolean add(Trade t) {
log(t);
return super.add(t);
}
}
ArrayList contains many internal methods calling add. Your override triggers multiple times, sometimes unexpectedly.
Result: duplicated logging, broken metrics, unstable behavior after JDK upgrades.
Composition pattern:
class RiskList {
private final List<Trade> trades = new ArrayList<>();
public boolean add(Trade t) {
log(t);
return trades.add(t);
}
}
Now behavior stays under control. No surprise calls. No dependency on internal structure.
Example
Order management systems track trades across pricing, margin, and settlement. Extending HashMap to inject audit logic breaks once JDK changes internal call paths. Using composition around Map preserves audit accuracy across releases.
Design for Inheritance or Prohibit It
Inheritance only works when a class is built for extension.
If subclassing stays allowed, documentation must explain:
Example of dangerous design:
class BaseRiskEngine {
public BaseRiskEngine() {
init();
}
protected void init() { }
}
Subclass:
class EquityRiskEngine extends BaseRiskEngine {
private MarketData data;
public EquityRiskEngine() {
data = load();
}
@Override
protected void init() {
data.validate();
}
}
Construction order:
The subclass method runs before subclass state exists.
This failure hides inside constructors.
Constructor Rule
Constructors must never call overridable methods.
If extension was not part of design, block it.
public final class PricingEngine {
}
Final communicates intent and protects behavior.
Example
Risk engines run during market open under tight latency budgets. Subclass constructors calling overridden methods produce partial initialization. Pricing fails before desks receive quotes.
Final classes prevent accidental extension across teams.
Design Rules
Takeaway
Inheritance multiplies coupling. Composition contains behavior.