Inheritance vs Composition
When to extend a class and when to hold one instead. Java's Stack extends Vector and lets callers remove from position 0, breaking last-in-first-out; a composed stack exposes only push, pop and size.
Inheritance models an is-a relationship and gives the subclass the parent's whole public surface; composition models has-a, holding an object and exposing only chosen methods. Prefer composition by default. Interviewers expect the reason: inherited methods can break invariants, as Java's Stack extends Vector and lets remove(0) take the first-pushed element, breaking last-in-first-out.
Inheritance models "is a"; composition models "has a" and delegates. Inheritance exposes every inherited method, so invariants can break: Java's Stack extends Vector and accepted remove(0), which returned the first-pushed element 1, breaking last-in-first-out. A composed stack exposes only push, pop and size. Prefer composition unless the subtype truly substitutes.
- Java's java.util.Stack extends Vector, so it inherits list methods; remove(0) on a stack holding 1 then 2 returned 1, breaking last-in-first-out order.
- A composed stack that holds an ArrayDeque and exposes only push, pop and size cannot be misused, because methods it does not expose do not exist.
- The fragile base class problem: a HashSet subclass overriding add and addAll double-counts, because HashSet.addAll internally calls add.
- Inheritance fits when the Liskov substitution principle holds, meaning any code written for the parent works correctly with the subclass.
- Java now recommends Deque, such as ArrayDeque, instead of java.util.Stack for stack use.
01Two ways to reuse code
Inheritance: class B extends A makes B a kind of A. It gets A's fields and methods, can override them, and can be used wherever an A is expected. Composition: B holds an A in a field and uses it to do part of its work, exposing only the methods B chooses.
Both give reuse. They differ in coupling. Inheritance ties the subclass to the parent's implementation and public surface for good. Composition ties B only to the parts of A it actually calls, and the held part can be replaced.
02When inheritance goes wrong: java.util.Stack
A stack is last-in, first-out: push and pop at one end. The original java.util.Stack extends Vector, a list, to reuse its storage. That means a Stack also inherits every list method, including ones that insert anywhere.
Stack<Integer> st = new Stack<>();
st.push(1); st.push(2);
st.remove(0); // inherited from Vector
System.out.println(st); // [2]In my test, remove(0) returned 1, the element pushed first, so the "stack" was drained from the bottom like a queue. Callers can also use add(1, 99) to insert in the middle (giving [1, 99, 2]), and the type no longer guarantees LIFO order. The class is documented as a stack but is also a list, and callers can rely on either. Java now recommends Deque (ArrayDeque) for stack use.
03The composed version
static class CompStack<T> {
private final Deque<T> d = new ArrayDeque<>();
void push(T t) { d.push(t); }
T pop() { return d.pop(); }
int size() { return d.size(); }
}The class holds an ArrayDeque but shows only push, pop and size. There is no way for callers to insert in the middle, because the method does not exist. After pushing 1 and 2, pop() returned 2. The storage can later change (an array, a linked list) without affecting any caller. This is encapsulation by construction: what you do not expose cannot be misused.
04The fragile base class problem
Subclasses depend on how the parent calls its own methods. A classic example from Effective Java: a HashSet subclass that counts additions by overriding add and addAll. HashSet.addAll internally calls add for each element, so each element is counted twice, once in the override of addAll and once through add. Whether the parent's addAll calls add is an implementation detail that could change in a future release, and the subclass would silently start counting correctly or incorrectly.
With composition (a wrapper that holds a set and forwards calls), the counting logic depends only on the public behaviour of the held set. Joshua Bloch's advice: design and document for inheritance, or else prohibit it.
05When inheritance is the right tool
Use inheritance when: the subclass really is a specialised kind of the parent in every context where the parent is used (the Liskov substitution principle); the parent was designed to be extended, with documented extension points; and you want polymorphism, treating different subtypes through one parent type. Frameworks use this: you extend a base Activity or HttpServlet to plug into a lifecycle.
A quick test: can you say "every B is an A, always, and any code written for A works correctly with B"? A square is not a rectangle in this sense when the shapes are mutable, as the SOLID topic shows with a numbers example. If the answer is "B just wants to use some of A's code", use composition.
06Composition in practice
Combine composition with interfaces. Define what you need as an interface, hold an implementation of it, and forward. This is delegation. A Car has an Engine and a Transmission rather than being a subclass of either. You can build a Car with an ElectricEngine or a PetrolEngine by passing it in, and test it with a fake engine. Many design patterns (Strategy, Decorator, Observer) are composition techniques at heart.
The cost of composition is writing forwarding methods when you want to expose the held object's whole interface. Some languages offer shortcuts: Kotlin has by delegation, and Lombok's @Delegate generates forwarders in Java.
07Common interview questions
Q: Inheritance or composition?
A: Prefer composition by default. Use inheritance for a true is-a relationship where substitutability holds and the parent was designed for extension.
Q: What is wrong with java.util.Stack?
A: It extends Vector, so it inherits list operations that let callers break last-in-first-out behaviour.
Q: What is the fragile base class problem?
A: A subclass depends on the parent's internal behaviour, so changes to the parent can break it.
Q: What is delegation?
A: A class forwarding work to a contained object rather than inheriting it.
Q: What does 'favour composition over inheritance' mean?
A: Build behaviour by combining objects, which is more flexible and less coupled than extending classes.
Q: Can you have both?
A: Yes. Implement interfaces for polymorphism and use composition for reuse.