Objects, Classes & OOP Design

Immutability & Class Design

Immutable objects are simple, safe to share, and inherently thread-safe. Minimize mutability by default: final fields, no setters, defensive copies at the boundaries — and reach for records when the class is pure data.
  • Immutable = no state change after construction: final class, final fields, no setters
  • Immutable objects need no synchronization — share them freely across threads
  • Defensive-copy mutable inputs and outputs (EJ 50)
  • Classes should be either designed for inheritance or final (EJ 19)
  • Minimize accessibility of everything (EJ 15)
  • The cost: a new object per "change" — usually negligible, occasionally not
An immutable class with functional "mutators"
public final class Money {
    private final BigDecimal amount;
    private final Currency currency;

    public Money(BigDecimal amount, Currency currency) {
        this.amount = Objects.requireNonNull(amount);
        this.currency = Objects.requireNonNull(currency);
    }

    public Money plus(Money other) {
        requireSameCurrency(other);
        return new Money(amount.add(other.amount), currency);  // return NEW object
    }
}

Why it pays: an immutable object has exactly one state, established by its constructor — no temporal reasoning, no aliasing surprises, no locks. It is the cheapest form of Thread Safety there is (JCiP: "immutable objects are always thread-safe"), and safe publication comes free via final fields (Sharing Objects).

When true immutability is impractical, minimize mutability: make every field final unless it must change, keep mutable state private, and provide the smallest possible mutation surface. A class with one mutable field is vastly easier to reason about than a JavaBean with ten setters.

Sources
  • Effective Java (3rd ed.)Items 15–17, 19, 50
  • Java Concurrency in PracticeCh. 3 — Sharing Objects (immutability)
  • Core Java, Volume I: Fundamentals (13th ed.)Ch. 4 — Objects and Classes