How does delegation let you reuse a class's behavior via composition instead of inheritance, and what does a forwarding wrapper look like?
answer
- Delegation = hold a field + forward calls
- ForwardingSet implements + InstrumentedSet decorates
- Avoids fragile base class (addAll double-count)
- Wraps ANY implementation, not one superclass
- Cost: boilerplate + SELF problem; JDK ex: java.io streams
basics
~20 sDelegation means your class holds another object as a field and forwards calls to it instead of extending it. You reuse the behavior without inheriting the parent's whole API or being tied to its internals. A forwarding class implements an interface and passes each method through to the held instance.
solid answer
~50 sDelegation is the implementation technique behind 'favor composition over inheritance': instead of extending a class to reuse it, you hold an instance as a field and forward the calls you want to its methods. This decouples you from the wrapped type's internals — you depend only on its public interface, can swap the implementation, and avoid the fragile-base-class problem and the leaked API that inheritance brings. The standard structure is a **forwarding class**: it implements the same interface as the wrapped object and delegates every method to the held instance; a thin **wrapper (decorator)** then extends the forwarding class and overrides just the methods it wants to augment. Effective Java's `ForwardingSet`/`InstrumentedSet` is the canonical example — it counts additions without subclassing `HashSet`, so it works over *any* `Set` and doesn't break when the parent's `addAll` internally calls `add`. The trade-off is boilerplate (one forwarding method per interface method, though IDEs and records/`@Delegate` help) and the SELF problem: the wrapped object's internal self-calls don't see the wrapper.
code
java · 21 lines// Reusable forwarding class: implements Set, delegates to a held Set
class ForwardingSet<E> implements Set<E> {
private final Set<E> s;
ForwardingSet(Set<E> s) { this.s = s; }
public boolean add(E e) { return s.add(e); }
public boolean addAll(Collection<? extends E> c) { return s.addAll(c); }
public int size() { return s.size(); }
// ... forward the rest of Set to s ...
}
// Thin wrapper adds behavior without subclassing HashSet
class InstrumentedSet<E> extends ForwardingSet<E> {
private int added = 0;
InstrumentedSet(Set<E> s) { super(s); }
@Override public boolean add(E e) { added++; return super.add(e); }
@Override public boolean addAll(Collection<? extends E> c) {
added += c.size(); return super.addAll(c); // no double count
}
int added() { return added; }
}
// Works over ANY Set: new InstrumentedSet<>(new TreeSet<>())go deeper
Understands that a class can hold another object and call its methods (a field plus forwarding) as an alternative to extending it, and can read a simple forwarding method.
Can write a forwarding wrapper, explains it reuses behavior via the delegate's public interface, and knows it works over any implementation of the interface rather than one fixed superclass.
Articulates the fragile-base-class failure (HashSet.addAll double-count), the Forwarding/Instrumented structure, the SELF/broken-callback caveat, and the boilerplate trade-off and its mitigations; relates it to Decorator.
Decides where delegation vs inheritance fits across a codebase, weighs boilerplate and the SELF problem against coupling and evolution risk, recognizes the pattern in framework/JDK design (java.io, proxies), and sets conventions (interface-first design, wrappers at extension seams) that keep hierarchies shallow and replaceable.
## Delegation: composition as a reuse mechanism We say 'favor composition over inheritance,' but composition is just *holding a field* — how do you actually **reuse behavior** that way? The answer is **delegation**: your object holds another object (the **delegate**) and **forwards** method calls to it. The outer object presents some behavior, but the real work is done by the held instance. ### The problem with inheriting for reuse Suppose you want a `Set` that counts how many elements were ever added. The tempting move is to subclass: ```java class CountingSet<E> extends HashSet<E> { int added = 0; @Override public boolean add(E e) { added++; return super.add(e); } @Override public boolean addAll(Collection<? extends E> c) { added += c.size(); return super.addAll(c); // BUG } } ``` This is subtly broken: `HashSet.addAll` is implemented by calling `add` internally, so each added element is counted **twice** — once in `addAll`, once in the `add` it invokes. You only know that by reading `HashSet`'s source, and a future JDK version could change it. This is the **fragile base class problem**: the subclass secretly depends on the superclass's implementation details. Inheritance also forces you to inherit `HashSet` specifically — you can't wrap a `TreeSet` or a third-party `Set`. ### The composition + delegation fix Build a **forwarding class** that *implements* `Set` and holds *a* `Set`, passing every call through: ```java class ForwardingSet<E> implements Set<E> { private final Set<E> s; // the delegate (composition) ForwardingSet(Set<E> s) { this.s = s; } public boolean add(E e) { return s.add(e); } public boolean addAll(Collection<? extends E> c) { return s.addAll(c); } public boolean remove(Object o) { return s.remove(o); } public int size() { return s.size(); } // ... forward every remaining Set method to s ... } ``` Then the actual feature is a thin **wrapper** (a Decorator) that extends the forwarder and overrides only what it augments: ```java class InstrumentedSet<E> extends ForwardingSet<E> { private int added = 0; InstrumentedSet(Set<E> s) { super(s); } @Override public boolean add(E e) { added++; return super.add(e); } @Override public boolean addAll(Collection<? extends E> c) { added += c.size(); return super.addAll(c); // CORRECT now } int added() { return added; } } ``` Why is `addAll` correct here? `super.addAll` forwards to the **delegate's** `addAll`, which calls the **delegate's** `add` — *not* `InstrumentedSet.add`. There's no double counting because the delegate has no idea it's wrapped. And `InstrumentedSet` now works over **any** `Set`: `new InstrumentedSet<>(new TreeSet<>())`, even a set from a library you don't control. ### What delegation buys you - **Decoupling from internals:** you depend only on the delegate's *public interface*, never its private implementation — no fragile-base-class breakage. - **Flexibility:** wrap any implementation of the interface; swap delegates at construction or even at runtime. - **No leaked API:** inheritance exposes every public superclass method whether it makes sense or not; delegation exposes only what you choose to forward. - **Composability:** wrappers stack (this is the **Decorator** pattern — `BufferedInputStream` wrapping a `FileInputStream` is delegation in the JDK). ### The costs - **Boilerplate:** one forwarding method per interface method. Mitigations: IDE generation, the forwarding-class-once-reused-many-times structure, Lombok's `@Delegate`, or interface default methods. - **The SELF problem (a.k.a. broken callbacks):** if the delegate calls one of *its own* methods internally, that call resolves on the **delegate**, not the wrapper — so a wrapper's override won't be seen by the delegate's internal self-calls. (This is exactly *why* `addAll` works above, but it can surprise you when you *want* the wrapper's behavior to be visible to inner calls; inheritance would have caught those via virtual dispatch.) - **Wrapper + listener pitfalls:** if you register a wrapped object as a callback, it may be invoked unwrapped. ### When inheritance is still right Delegation is for an 'IS-A is awkward' situation. When there is a genuine, stable IS-A relationship and you control both classes (a true subtype, or a framework's intended extension via template methods), inheritance is appropriate. The rule is a *default*, not a prohibition: **favor** composition, but use inheritance when the substitutability is real. ### One-line takeaway Delegation = compose (hold a field) + forward (call its methods). It reuses behavior through an object's public interface instead of inheriting its implementation, trading a little boilerplate for far looser coupling.
- What is the SELF problem in delegation-based wrappers?When the delegate calls one of its own methods internally, that call binds to the delegate, not to the wrapper, so the wrapper's overridden version isn't invoked for the delegate's inner self-calls. With inheritance, virtual dispatch would route such self-calls back to the subclass; delegation deliberately doesn't, which is sometimes what you want and sometimes a surprise.
- How does this relate to the Decorator pattern and a JDK example?A delegating wrapper that implements the same interface as the thing it wraps and adds behavior is exactly the Decorator pattern. The java.io stream hierarchy is the classic JDK case: BufferedInputStream wraps and delegates to an underlying InputStream, adding buffering without subclassing the concrete stream type.
A receptionist (wrapper) takes your request and forwards it to the right specialist (delegate). The receptionist isn't a doctor (no inheritance) but provides the doctor's service plus their own logging of who called.
saying these in an interview costs you the question
- Claiming delegation requires inheriting the delegate's class — it forwards to a held field, no extends needed.
- Saying subclassing HashSet to instrument addAll is fine — it double-counts due to internal self-calls (fragile base class).
- Forgetting that a forwarding wrapper only exposes the methods you forward, unlike inheritance which leaks the whole superclass API.
- Assuming the delegate's internal self-calls see the wrapper's overrides (the SELF problem says they don't).