Immutability and Defensive Copies

OOP · lesson 11 of 43 · 4 min read

How to build objects that cannot change, and why final alone is not enough to stop mutation.

Open this lesson in the learning hub

Key points

  • Immutable means no field changes after construction. Such objects are thread safe and free to share anywhere.
  • final freezes the reference, not the object behind it. A final List field can still be added to.
  • Copy mutable arguments in the constructor, or the caller keeps a handle on your internals and edits them later.
  • Copy or wrap on the way out too. List.copyOf copies and returns an unmodifiable list in one step.
  • A record gives final fields, but no deep copy. A record holding a List is still mutable inside.
  • To change an immutable object, return a new one. String and LocalDate work exactly that way.

Example

import java.util.ArrayList;
import java.util.List;

public class Main {
    static final class Team {                  // final: no subclass can break the rules
        private final String name;
        private final List<String> members;    // final ref to a MUTABLE object

        Team(String name, List<String> members) {
            this.name = name;
            this.members = List.copyOf(members);   // defensive copy on the way IN
        }

        List<String> members() { return members; } // already unmodifiable on the way OUT

        @Override public String toString() { return name + members; }
    }

    public static void main(String[] args) {
        List<String> input = new ArrayList<>(List.of("Ada", "Grace"));
        Team team = new Team("Core", input);

        input.add("Mallory");                  // caller mutates their own list
        System.out.println(team);              // Core[Ada, Grace] - untouched

        try {
            team.members().add("Mallory");
        } catch (UnsupportedOperationException e) {
            System.out.println("the exposed list cannot be mutated either");
        }
    }
}

final protects the reference; only a defensive copy protects the object.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the OOP course, and every lesson in it is listed on the OOP contents page.