Question 10 · Section 18

What is Composition and Inheritance

White-box reuse — parent internals are visible (inheritance). Black-box reuse — only public interface (composition). Devirtualization — JIT replaces virtual call with direct. Mo...

Language versions: English Russian Ukrainian

🟢 Junior Level

Composition and inheritance are the two main ways to organize connections between classes in OOP. Inheritance creates an “is-a” (IS-A) relationship: one class becomes a special case of another. Composition creates a “has-a” (HAS-A) relationship: one object contains another object as a part.

Simple analogy: Inheritance — like inheriting traits from parents (you are a human). Composition — like buying a phone (you have a phone, can replace it).

Inheritance example (IS-A):

public class Animal {
    public void eat() { System.out.println("Eating..."); }
}

public class Dog extends Animal {  // Dog IS-A Animal
    public void bark() { System.out.println("Woof!"); }
}

Composition example (HAS-A):

public class Engine {
    public void start() { System.out.println("Engine started"); }
}

public class Car {
    private final Engine engine;  // Car HAS-A Engine

    public Car(Engine engine) {
        this.engine = engine;
    }

    public void start() {
        engine.start();  // Delegation
    }
}

When to use:

  • Inheritance: when there’s a real type hierarchy (Dog IS-A Animal)
  • Composition: in most other cases — it’s a more flexible approach

🟡 Middle Level

How It Works

Inheritance implements the “is-a” (IS-A) relationship and uses virtual method tables (VTable) at the JVM level. The subclass gains access to protected fields of the parent — this is “white-box reuse”.

Composition implements the “has-a” (HAS-A) relationship through method calls on another object. Interaction is only through the public interface — this is “black-box reuse”.

White-box reuse — parent internals are visible (inheritance). Black-box reuse — only public interface (composition). Devirtualization — JIT replaces virtual call with direct. Monomorphic = one call type → inlining. Megamorphic = 3+ types → table lookup.

Practical Application

Why Effective Java says “Favor Composition over Inheritance”:

Criterion Inheritance Composition
Coupling Static (compile-time) Dynamic (runtime)
Encapsulation Broken (protected fields visible) Preserved
Flexibility Cannot change parent Can swap component
Testability Hard to mock Easy to substitute mock
Fragility Parent change breaks all subclasses Isolated changes

Common Mistakes

Mistake Solution
Inheritance for code reuse Use composition + delegation
Deep hierarchy (5+ levels) Refactor toward composition
ArrayListMySpecialList via inheritance MySpecialList with a List field inside

Alternatives to Inheritance

  1. Strategy: instead of SmsNotification extends NotificationNotificationService with a SenderStrategy field
  2. Decorator: wrapping an object to add functionality (logging, caching)
  3. Delegation: a class implements an interface but calls delegate methods

When NOT to Use Composition

  • Clean type hierarchies (exceptions: IOExceptionSocketException)
  • Frameworks with Template Method pattern
  • When polymorphism and working through a common base type is needed

Template Method vs Strategy: Template Method = you control the algorithm, subclasses control steps. Strategy = client chooses the entire algorithm. Template Method — when the algorithm skeleton is stable. Strategy — when the entire algorithm may change.


🔴 Senior Level

Internal Implementation at JVM Level

Inheritance and VTable: When calling a virtual method, the JVM uses a virtual method table (VTable). Each class has its own VTable with pointers to method implementations. With deep hierarchies (5+ levels), it’s harder for the JIT compiler to perform devirtualization and inlining.

// Inheritance: invokevirtual, VTable lookup
class Parent { void foo() {} }
class Child extends Parent { @Override void foo() {} }
// parent.foo() → invokevirtual → VTable lookup

// Composition with final: JIT can inline
class Service {
    private final Delegate delegate;  // final → monomorphic call
    void execute() { delegate.run(); } // JIT inlines → direct call
}

Architectural Trade-offs

Inheritance:

  • ✅ Pros: Out-of-the-box polymorphism, code reuse, Template Method
  • ❌ Cons: Fragile base class, implementation leakage, static coupling

Composition:

  • ✅ Pros: Dynamic behavior, testability (easy to mock), loose coupling
  • ❌ Cons: More boilerplate code (delegation), more objects in heap

Edge Cases

  1. Fragile Base Class Problem: changing addAll in the parent, which calls add, breaks the subclass that overrode both methods
    • Solution: Composition + Forwarding
    • This is a HashSet implementation detail: addAll calls add internally. But you can’t rely on this — in another implementation it may be different. That’s why inheritance from classes not designed for it is fragile.
  2. Shared State in Composition: multiple delegates work with shared data
    • Solution: Explicit context passing or State Object
  3. Circular Delegation: A → B → A → StackOverflowError
    • Solution: Architectural analysis, ArchUnit rules

Performance

Metric Inheritance Composition
Object size All parent fields in one object Separate objects + headers
Method call invokevirtual (VTable lookup) invokevirtual + possible inlining
JIT optimization Harder with deep hierarchy Easier with final fields
Memory ~16 byte header + all fields ~16 bytes × N objects + refs
  • Inlining: JIT easily inlines final delegates → overhead ~0
  • VTable: At depth 5+ levels, megamorphic calls → deoptimization

Thread Safety

  • Inheritance: synchronized on parent method blocks the entire object
  • Composition: Different locks can be used for different delegates → finer-grained concurrency

Production Experience

Refactoring in an enterprise project: NotificationService with 2000 lines and subclasses EmailNotification, SmsNotification, PushNotification. Problem: adding a new strategy required changing the base class.

Refactoring: extracted NotificationStrategy interface, each type — a separate implementation. NotificationService became a coordinator. Result: new types are added without modifying existing code (OCP).

Monitoring

ArchUnit rules:

@ArchTest
static void no_deep_inheritance = classes()
    .should().notHaveSimpleNameMatching(".*Impl.*")
    .andShould().haveLessThanNAncestors(3);

SonarQube: Depth of Inheritance Hierarchy > 5 → Code Smell

Best Practices for Highload

  • final delegates for better inlining
  • Package-private for internal components
  • Sealed classes (Java 17+) for controlled inheritance

Sealed classes are available since Java 17. For Java 8/11, use final classes and document which classes are intended for inheritance.

  • Composition over Inheritance as default choice

🎯 Interview Cheat Sheet

Must know:

  • Inheritance = IS-A (static coupling, white-box reuse), Composition = HAS-A (dynamic, black-box)
  • “Favor Composition over Inheritance” — Effective Java, Josh Bloch
  • Composition preserves encapsulation, inheritance breaks it (protected fields visible)
  • JIT easily inlines final delegates → composition overhead ~0
  • Inheritance: fragile base class, implementation leakage, static coupling
  • Fragile Base Class: changing addAll in parent breaks subclass that overrode add
  • Sealed classes (Java 17+) for controlled inheritance

Common follow-up questions:

  • Why is composition better? — Dynamic component replacement, testability (easy to mock), loose coupling
  • When is inheritance acceptable? — True IS-A, frameworks with Template Method, common infrastructure fields (BaseEntity)
  • What is devirtualization? — JIT replaces virtual call with direct when it knows the exact type
  • VTable overhead with inheritance? — 5+ levels → megamorphic calls → JIT deoptimization

Red flags (DO NOT say):

  • “Inheritance is the best way to reuse code” (main cause of fragility)
  • “Deep hierarchy is a sign of good design” (5+ levels — code smell)
  • “Composition is always better” (there are legit inheritance cases: Template Method, IS-A)

Related topics:

  • [[11. When is it better to use composition instead of inheritance]]
  • [[12. What is delegation in OOP]]
  • [[5. What is Liskov Substitution principle]]
  • [[6. Give an example of Liskov Substitution principle violation]]