Java Composition vs Inheritance: Learn the Key Differences with Examples

0

Inheritance is powerful, but it is not automatically the best way to reuse code. In real software development, one of the most important design decisions is choosing between inheritance and composition.

The difference can be summarized with a simple question: does one class truly represent a specialized version of another class, or does one class simply need to use another object to accomplish its work?

Inheritance models an is-a relationship, while composition models a has-a relationship.

What Is Inheritance?

Inheritance allows a child class to extend a parent class and reuse its accessible state and behavior. The child becomes a specialized form of the parent.

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

class Dog extends Animal {
    void bark() {
        System.out.println("Dog barks");
    }
}

A dog is an animal, so the inheritance relationship makes semantic sense. The child can reuse behavior from the parent while adding specialized behavior.

What Is Composition?

Composition means a class contains or uses an object of another class to provide part of its functionality. Instead of becoming a specialized version of another class, the class collaborates with another object.

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

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

    void startCar() {
        engine.start();
        System.out.println("Car starts");
    }
}

A car is not an engine. A car has an engine. That makes composition a more natural relationship here.

Is-a → Think about inheritance.

Has-a → Think about composition.

Real-World Analogy

Imagine building a smartphone. A smartphone is not a battery, camera, or speaker. Instead, it contains these components and uses them to provide its features.

That is composition: the smartphone delegates specific responsibilities to objects representing its components.

By contrast, if Smartphone were a specialized type of Device, inheritance could make sense because the relationship describes an is-a hierarchy.

Inheritance Example

class Employee {
    void work() {
        System.out.println("Employee works");
    }
}

class Developer extends Employee {
    void writeCode() {
        System.out.println("Developer writes code");
    }
}

A developer is an employee, so inheritance expresses a meaningful specialization relationship.

Composition Example

class Logger {
    void log(String message) {
        System.out.println(message);
    }
}

class OrderService {
    private Logger logger;

    OrderService(Logger logger) {
        this.logger = logger;
    }

    void createOrder() {
        logger.log("Order created");
    }
}

An OrderService is not a logger. It simply uses a logger. Composition keeps those responsibilities separate and allows the logger implementation to be changed without creating an inheritance relationship.

Composition Through Dependency Injection

Composition becomes especially flexible when dependencies are supplied from outside instead of being created directly inside the class.

class EmailSender {
    void send(String message) {
        System.out.println("Email: " + message);
    }
}

class NotificationService {
    private EmailSender sender;

    NotificationService(EmailSender sender) {
        this.sender = sender;
    }

    void notifyUser() {
        sender.send("Welcome!");
    }
}

The service receives its collaborator through the constructor. This is a simple form of dependency injection and makes the class easier to test, replace, and maintain.

Why Composition Is Often More Flexible

Inheritance creates a strong relationship between parent and child. A child depends on the structure and behavior of its parent. If the parent changes, the child may be affected.

Composition usually creates a looser relationship. A class depends on an object that performs a specific responsibility, and that object can often be replaced without changing the entire class hierarchy.

Aspect Inheritance Composition
Relationship Is-a Has-a / uses-a
Reuse mechanism Extending a class Collaborating with objects
Coupling Usually tighter Usually looser
Runtime flexibility More limited Generally higher
Best suited for True specialization Combining independent responsibilities

A Design Mistake: Inheritance Just for Code Reuse

One of the most common design mistakes is using inheritance simply because it avoids writing duplicate code.

Suppose two unrelated classes need logging functionality. Making both classes extend a Logger class may appear convenient, but the relationship is conceptually wrong. A service is not a logger, and a report generator is not a logger.

// Poor relationship
class ReportService extends Logger {
}

// Better relationship
class ReportService {
    private Logger logger;

    ReportService(Logger logger) {
        this.logger = logger;
    }
}

The second design says exactly what the code means: the service uses a logger.

Inheritance Can Be the Right Choice

Composition is not a replacement for inheritance in every situation. Inheritance is appropriate when the child genuinely represents a specialized form of the parent and the parent-child contract is stable and meaningful.

class Shape {
    void draw() {
        System.out.println("Drawing shape");
    }
}

class Circle extends Shape {
    @Override
    void draw() {
        System.out.println("Drawing circle");
    }
}

A circle is a shape, so the inheritance relationship is meaningful. The design also supports polymorphism because different shapes can be treated through the common Shape type.

Composition and Testability

Composition can make unit testing easier because collaborators can be replaced with controlled test implementations.

class PaymentGateway {
    void pay() {
        System.out.println("Payment processed");
    }
}

class CheckoutService {
    private PaymentGateway gateway;

    CheckoutService(PaymentGateway gateway) {
        this.gateway = gateway;
    }

    void checkout() {
        gateway.pay();
    }
}

Because the gateway is supplied from outside, a test can provide a controlled implementation instead of forcing the service to create a real payment gateway internally. This separation becomes especially valuable in larger applications.

Composition vs Inheritance: A Practical Decision

Before choosing inheritance, ask whether the child truly satisfies the parent's meaning. If removing the parent relationship would make the model conceptually incorrect, inheritance may be appropriate.

If the class simply needs another object to perform a task, composition is usually the more natural starting point.

Question Likely Choice
Is the child genuinely a specialized form of the parent? Inheritance
Does the class simply use another object's functionality? Composition
Do you need to swap implementations easily? Composition
Do multiple classes share a meaningful common abstraction? Inheritance may fit
Are you using inheritance only to reuse a few methods? Consider composition

Common Beginner Mistakes

  • Using inheritance merely to reuse code.
  • Forcing an is-a relationship where only a has-a relationship exists.
  • Creating deep inheritance hierarchies when simple object collaboration would be clearer.
  • Ignoring composition because inheritance appears easier initially.
  • Assuming composition and inheritance are competitors where only one can ever be used. Real applications often use both.

Best Practice

Choose inheritance when you are modeling a genuine specialization and need polymorphic behavior through a common parent type. Choose composition when one class needs another object to perform a responsibility.

A useful professional habit is to start by asking whether composition can express the design cleanly. If inheritance provides a stronger and more meaningful abstraction, use it deliberately rather than automatically.

Interview Insight

A strong interview answer is: "Inheritance represents an is-a relationship and allows a subclass to extend a parent type. Composition represents a has-a relationship where a class uses other objects to provide functionality. Composition often provides lower coupling and greater flexibility, while inheritance is valuable when a genuine specialization and polymorphic relationship exist."

Final Takeaway

Inheritance and composition are both powerful design tools, but they solve different problems. Use inheritance to represent a meaningful is-a relationship and specialization; use composition when a class has-a or uses another object. The best design is not the one that uses the most inheritance or the most composition—it is the one that expresses the domain clearly, keeps responsibilities focused, and remains easy to change as the application grows.

Tags

Post a Comment

0Comments
Post a Comment (0)