Menu
Coddy logo textTech

Composition vs Inheritance

Part of the Object Oriented Programming section of Coddy's Java journey — lesson 47 of 87.

Inheritance creates an "is-a" relationship: a Dog is an Animal. But what if a class needs functionality from something it isn't? This is where composition comes in, establishing a "has-a" relationship instead.

With composition, a class contains instances of other classes as fields rather than extending them:

// Inheritance approach: Car IS an Engine (doesn't make sense)
class Car extends Engine { }

// Composition approach: Car HAS an Engine (makes sense)
class Car {
    private Engine engine;
    
    public Car(Engine engine) {
        this.engine = engine;
    }
    
    public void start() {
        engine.ignite();
    }
}

Composition offers several advantages. You can change the composed object at runtime, such as swapping engines.

You can also combine behaviors from multiple classes, something inheritance doesn't allow in Java. The composed class only exposes the methods it chooses, providing better encapsulation.

A practical guideline: use inheritance when there's a genuine "is-a" relationship and the subclass truly represents a specialized version of the parent.

Use composition when you need to use another class's functionality without being that type. Many experienced developers follow the principle: "favor composition over inheritance" because it leads to more flexible, maintainable code.

challenge icon

Challenge

Easy

Let's build a computer system using composition to demonstrate the "has-a" relationship. Instead of a Computer inheriting from its components (which wouldn't make sense), your computer will contain the parts it needs to function.

You'll organize your code across three files:

  • Processor.java: Create a class representing a CPU. A Processor has two private fields: brand (String) and speedGHz (double). Include a constructor to initialize both fields and getter methods for each. Add a process() method that returns a String: [brand] processing at [speedGHz] GHz
  • Memory.java: Create a class representing RAM. A Memory has two private fields: type (String, like "DDR4" or "DDR5") and sizeGB (int). Include a constructor, getters, and a load() method that returns: Loading [sizeGB]GB [type] memory
  • Computer.java: Here's where composition shines! Your Computer class should have a Processor and a Memory as private fields—it doesn't extend them, it contains them. Include a constructor that accepts both components. Add a boot() method that returns a multi-line String showing the computer starting up by using its components:
    Booting computer...
    [processor.process() result]
    [memory.load() result]
    System ready!
    Also add a getSpecs() method that returns: Specs: [processor brand] CPU, [memory sizeGB]GB [memory type]
  • Main.java: Bring your composed system to life! You'll receive four inputs: processor brand (String), processor speed (double), memory type (String), and memory size (int).

    Create a Processor and Memory with these values, then compose them into a Computer. Print the result of calling boot(), then print the result of getSpecs().

You will receive four inputs in order: processor brand, processor speed (GHz), memory type, and memory size (GB).

Notice how the Computer delegates work to its components rather than trying to be them—this is the power of composition! The Computer can use any Processor or Memory you give it, making the design flexible and realistic.

Cheat sheet

Composition creates a "has-a" relationship where a class contains instances of other classes as fields rather than extending them.

Inheritance vs Composition:

// Inheritance: Car IS an Engine (doesn't make sense)
class Car extends Engine { }

// Composition: Car HAS an Engine (makes sense)
class Car {
    private Engine engine;
    
    public Car(Engine engine) {
        this.engine = engine;
    }
    
    public void start() {
        engine.ignite();
    }
}

Advantages of Composition:

  • Objects can be changed at runtime
  • Can combine behaviors from multiple classes
  • Better encapsulation - only expose chosen methods
  • More flexible and maintainable code

When to Use:

  • Inheritance: Use when there's a genuine "is-a" relationship and the subclass is a specialized version of the parent
  • Composition: Use when you need another class's functionality without being that type

Best Practice: "Favor composition over inheritance"

Try it yourself

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // Read inputs
        String processorBrand = scanner.nextLine();
        double processorSpeed = Double.parseDouble(scanner.nextLine());
        String memoryType = scanner.nextLine();
        int memorySize = Integer.parseInt(scanner.nextLine());
        
        // TODO: Create a Processor with the brand and speed
        
        // TODO: Create a Memory with the type and size
        
        // TODO: Create a Computer by composing the Processor and Memory
        
        // TODO: Print the result of boot()
        
        // TODO: Print the result of getSpecs()
    }
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming