Immutability and Defensive Copies
How to build objects that cannot change, and why final alone is not enough to stop mutation.
Open this lesson in the learning hubKey points
- Immutable means no field changes after construction. Such objects are thread safe and free to share anywhere.
finalfreezes the reference, not the object behind it. Afinal Listfield 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.copyOfcopies and returns an unmodifiable list in one step. - A
recordgives final fields, but no deep copy. A record holding aListis still mutable inside. - To change an immutable object, return a new one.
StringandLocalDatework 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.