Composition vs Inheritance in Java – When to Prefer Composition

Illustration for Composition vs Inheritance in Java – When to Prefer Composition
By Last updated:

Introduction

Composition and inheritance are two fundamental approaches to code reuse in Java. While both allow sharing behavior, knowing when to prefer composition is crucial for building flexible, maintainable software.

Why It Matters

  • Impacts code maintainability and flexibility.
  • Influences class design and hierarchy depth.
  • Prevents tight coupling and promotes reusability.

When to Use

  • Inheritance: When there is a clear "is-a" relationship and behavior is stable.
  • Composition: When you want to reuse functionality without creating rigid hierarchies.

Core Concepts

What is Inheritance?

  • A mechanism to acquire properties and behavior from a parent class.
  • Achieved using extends keyword.
class Vehicle {
    void move() { System.out.println("Vehicle moving"); }
}

class Car extends Vehicle {
    void honk() { System.out.println("Car honking"); }
}

What is Composition?

  • Building classes using references to other classes (has-a relationship).
  • Achieved via member objects.
class Engine {
    void start() { System.out.println("Engine starting"); }
}

class Car {
    private Engine engine = new Engine();

    void drive() { engine.start(); }
}

Real-World Analogy

  • Inheritance: A sports car is a type of car.
  • Composition: A car has an engine.

Comparison Table

Aspect Inheritance Composition
Relationship Is-a Has-a
Flexibility Rigid hierarchy High flexibility
Code Reuse Via superclass Via contained objects
Coupling Tight Loose
Overriding Possible Requires delegation

Real-World Use Cases

  • Inheritance:

    • GUI frameworks (JFrame extends Container).
    • Domain hierarchies like Animal -> Dog.
  • Composition:

    • Car with engine.
    • Order with payment service.
    • Decorator design pattern.

Common Mistakes & Anti-Patterns

  1. Overusing Inheritance:
    • Leads to deep, fragile hierarchies.
  2. Violating Liskov Substitution Principle:
    • Subclasses that don’t behave like parent classes.
  3. Forgetting Delegation in Composition:
    • Wrapping without exposing necessary behavior.

Performance & Memory Implications

  • Both have minimal direct performance impact.
  • Composition provides more runtime flexibility by allowing swapping of components.
  • Inheritance creates stronger compile-time binding.

Best Practices

  • Favor composition for code reuse unless a true is-a relationship exists.
  • Keep inheritance hierarchies shallow.
  • Use interfaces with composition for polymorphism.
  • Apply design patterns like Strategy and Decorator with composition.

Java Version Relevance

Version Change
Java 8+ Lambdas make composition more powerful with functional interfaces
Java 14+ Records promote composition with immutability

Code Example: Prefer Composition

interface PaymentProcessor {
    void process(double amount);
}

class CreditCardProcessor implements PaymentProcessor {
    public void process(double amount) {
        System.out.println("Processing credit card payment: " + amount);
    }
}

class OrderService {
    private final PaymentProcessor processor;

    OrderService(PaymentProcessor processor) {
        this.processor = processor;
    }

    void checkout(double amount) {
        processor.process(amount);
    }
}
  • OrderService uses composition with PaymentProcessor, allowing easy swapping of implementations.

Conclusion & Key Takeaways

  • Inheritance is for "is-a" relationships; composition is for "has-a".
  • Prefer composition to avoid rigid hierarchies and tight coupling.
  • Use delegation with composition to expose required behavior.

FAQ

  1. What is composition in Java?
    Using member objects to build functionality (has-a relationship).

  2. What is inheritance?
    Sharing behavior via parent-child classes (is-a relationship).

  3. Why prefer composition over inheritance?
    It provides more flexibility and reduces coupling.

  4. Can I mix composition and inheritance?
    Yes, use both when appropriate.

  5. Does composition affect performance?
    Minimal, often negligible compared to design benefits.

  6. Is delegation required in composition?
    Yes, to expose wrapped behavior.

  7. What’s a common anti-pattern with inheritance?
    Deep class hierarchies and misuse of is-a relationships.

  8. How does composition help testing?
    Allows mocking/swapping dependencies easily.

  9. Can interfaces be used with composition?
    Yes, to achieve polymorphism.

  10. Which design patterns use composition?
    Strategy, Decorator, Adapter, Composite.