Lesson 24 / 38

Composition & Immutable Classes

Weigh composition against inheritance for code reuse, then design a genuinely immutable class that is safe to share across threads without defensive copying headaches.

Composition over inheritance

Inheritance couples a subclass tightly to its parent's implementation — a change in the parent can silently break every child. Composition (holding another object as a field and delegating to it) keeps the relationship explicit and swappable, and is the safer default for code reuse that isn't a true "is-a".

Delegation in practice

Playlist reuses ArrayList's storage without inheriting its entire (huge) public API — callers only see the small surface Playlist chooses to expose.

class Playlist {
    private final List<String> tracks = new ArrayList<>();  // composition

    void add(String track) { tracks.add(track); }
    String get(int i) { return tracks.get(i); }
    int size() { return tracks.size(); }
}

A truly immutable class

Make the class final, all fields private final, set them only in the constructor, never expose a mutable field directly (defensively copy collections/arrays in and out), and provide no setters.

public final class Point {
    private final int x, y;

    public Point(int x, int y) { this.x = x; this.y = y; }
    public int getX() { return x; }
    public int getY() { return y; }

    public Point translated(int dx, int dy) {
        return new Point(x + dx, y + dy);   // return a NEW instance
    }
}

Why immutability pays off

Immutable objects are automatically thread-safe (no lock needed — state never changes), safe to use as HashMap keys, and easy to reason about. Java's own String, Integer, and LocalDate are all immutable for exactly these reasons.