When is inheritance the right choice over composition in Java, and what tests help you decide?
answer
- Ask literally: is every B really an A?
- LSP — substitutable without surprises (Square/Rectangle)
- Design & document for inheritance, or forbid it (final)
- Inherit type (interface) freely; inherit concrete code warily
- Stack/Vector and Properties/Hashtable = IS-A gone wrong
basics
~20 sUse inheritance only when the child truly IS-A parent and can be used anywhere the parent is expected, and the parent was designed to be extended. Otherwise, compose by holding the other object as a field.
solid answer
~50 sComposition is the default, but inheritance is correct in specific cases. The primary test is a genuine IS-A relationship that satisfies the Liskov Substitution Principle: the subtype must work correctly anywhere the supertype is expected, with no surprising behavior. A practical screen from Effective Java is to ask 'is B really an A?' — if you'd never substitute the subtype for the supertype, you don't have IS-A. The second condition is that the superclass must be designed and documented for extension (its self-use of overridable methods is part of its contract), or be a class you own and control. Inheriting type from interfaces is always fine and encouraged. Also weigh: inheritance across package boundaries to a concrete class is dangerous; if the parent's API has methods that don't make sense for the child, that's a red flag. When in doubt, compose — you can always wrap, and you keep your single inheritance slot free.
go deeper
Knows the rough rule: use extends only when the child is a kind of the parent; otherwise hold it as a field.
Applies the IS-A test and recognizes leaky examples like Stack/Vector; defaults to composition when unsure.
Reasons with LSP and the 'design for inheritance or prohibit it' rule, distinguishes type vs implementation inheritance, and justifies the choice per case.
Sets codebase conventions (final by default, sealed hierarchies, documented extension points) and evaluates inheritance choices against long-term API evolution and substitutability guarantees.
## Framing "Favor composition over inheritance" is a *default*, not an absolute ban. The skill is knowing the cases where inheritance is the *better* tool, and applying explicit tests so the choice isn't accidental. ## Test 1 — Is there a genuine IS-A relationship? Inheritance (`class B extends A`) declares "every B **is an** A." Before using it, ask Joshua Bloch's question literally: *"Is every B really an A?"* If you can't honestly answer yes, you don't have inheritance — you have a HAS-A or USES-A relationship, which means **compose**. Classic violations: - `Stack extends Vector` — a stack is *not* a general-purpose growable array; inheriting leaks `get(int)`/`add(index, e)` and lets callers corrupt LIFO order. - `Properties extends Hashtable` — `Properties` is meant to hold String→String, but inheriting `Hashtable` lets callers insert any object via `put`, bypassing the intended invariant. Both are cases where IS-A was *assumed* but the supertype's API didn't actually fit; composition would have hidden the unwanted methods. ## Test 2 — Liskov Substitution Principle (LSP) LSP states: **a subtype must be usable anywhere its supertype is expected without breaking the program's expectations.** If substituting `B` for `A` could surprise a caller — different preconditions, weaker guarantees, thrown exceptions the supertype never throws — then `B extends A` is wrong even if it *feels* like IS-A. A famous example: making `Square extends Rectangle`. A `Rectangle` lets you set width and height independently; a `Square` cannot honor that without violating its own invariant, so it breaks any code that relies on `Rectangle`'s contract. Composition (a `Square` that *has* dimensions) sidesteps the conflict. ## Test 3 — Was the superclass designed for extension? Even for a true IS-A, inheriting a **concrete** class is safe only when that class **documents its self-use of overridable methods** — i.e. it tells you which methods it calls internally so your overrides won't cause surprises (the `HashSet.addAll`-calls-`add` problem). *Effective Java* Item 19 says: *design and document for inheritance, or else prohibit it* (mark the class `final` or hide its constructor). So: - Extending classes **you own** and can keep in sync: acceptable. - Extending a third-party concrete class purely for reuse, across a package boundary, where its self-use is undocumented: dangerous — prefer composition. ## Test 4 — Inheritance of *type* vs *implementation* There are two kinds of inheritance, and they have different risk: - **Inheritance of implementation** (`extends` a concrete class) — the risky kind the guideline warns about. - **Inheritance of type** (`implements` an interface, or `extends` an interface/abstract type) — *encouraged*. Implementing `Comparable`, `Runnable`, or a domain interface is always fine; it's a contract, not borrowed code. Using a well-documented abstract skeletal class like `AbstractList` is also fine because it was *built* to be extended. ## A quick decision checklist 1. Is it genuinely IS-A and Liskov-substitutable? If no → compose. 2. Does every method of the parent make sense on the child? If no → compose. 3. Is the parent designed/documented for extension, or owned by you? If no → compose. 4. Are you inheriting *type* (interface)? Then inherit freely. 5. Still unsure? Compose — it's reversible and keeps the single `extends` slot free. ## Terms - **IS-A / HAS-A:** subtype relationship vs ownership/usage relationship. - **Liskov Substitution Principle (LSP):** subtypes must be safely substitutable for their supertypes. - **Self-use:** a class calling its own overridable methods internally; must be documented for safe subclassing. - **Inheritance of type vs implementation:** implementing a contract (interface) vs borrowing concrete code.
- Why is Square extends Rectangle a classic LSP violation?Rectangle's contract lets width and height vary independently. A Square must keep them equal, so a setWidth that leaves height unchanged breaks the Square invariant; code written against Rectangle's contract gets surprising results. Substituting a Square where a Rectangle is expected violates expectations, so the inheritance is unsound.
- What does 'design and document for inheritance, or else prohibit it' mean in practice?Either document a concrete class's self-use of overridable methods (and provide hooks) so subclasses can override safely, or make the class final / hide its constructor so it can't be subclassed at all. Leaving a concrete class accidentally extensible invites fragile-base-class bugs.
saying these in an interview costs you the question
- Treating inheritance as forbidden — interfaces and designed-for-extension abstract classes are the right tool.
- Using inheritance whenever there is shared code, regardless of IS-A.
- Ignoring LSP and assuming any 'feels like a kind of' relationship justifies extends.
- Subclassing a third-party concrete class for reuse without checking it's documented for extension.