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.