Unravel โบ Java concepts โบ OOP and types
Immutability and final classes
An immutable object never changes after construction.
Learn these first
At a glance
A printed book versus a whiteboard: everybody can read the book at once and trust it, while a whiteboard needs rules about who may write on it and when.
- 1Recipe: make the class final, fields private final, no setters, set everything in the constructor, and copy any mutable input (List.copyOf(items)) and mutable output
- 2"Changing" such an object means creating a new one: String.toUpperCase(), LocalDate.plusDays() and BigDecimal.add() all return new objects
- 3Records give you this shape in one line, but only if their components are themselves immutable
- 4Collections.unmodifiableList(x) is a read-only view: the list still changes if the original doesList.copyOf(x) is a true immutable copy.
- Immutable objects are automatically thread safe, safe as map keys and cache entries, and simple to reason about
- final on a field gives a visibility guarantee: after the constructor finishes, every thread sees the final fields correctly, even if the object was published through a data race
- The Java memory model treats final fields specially: a freeze action at the end of the constructor ensures that other threads that see the reference also see the initialised final fields
import java.util.*;public final class Invoice { private final String id; private final List<String> lines; public Invoice(String id, List<String> lines) { this.id = Objects.requireNonNull(id); this.lines = List.copyOf(lines); } public List<String> lines() { return lines; } public Invoice withLine(String line) { List<String> more = new ArrayList<>(lines); more.add(line); return new Invoice(id, more); }}- line 6 uses requireNonNull, the core of Immutability and final classes
- line 7 uses copyOf, the core of Immutability and final classes
Read the full explanation (about 1 min)
An immutable object never changes after construction. It can be shared freely between methods and threads without locks, and cannot be corrupted by a caller.
A mental model
A printed book versus a whiteboard: everybody can read the book at once and trust it, while a whiteboard needs rules about who may write on it and when.
How it works
Recipe: make the class final, fields private final, no setters, set everything in the constructor, and copy any mutable input (List.copyOf(items)) and mutable output.
"Changing" such an object means creating a new one: String.toUpperCase(), LocalDate.plusDays() and BigDecimal.add() all return new objects.
Records give you this shape in one line, but only if their components are themselves immutable.
Collections.unmodifiableList(x) is a read-only view: the list still changes if the original does. List.copyOf(x) is a true immutable copy.
Going deeper
Immutable objects are automatically thread safe, safe as map keys and cache entries, and simple to reason about. The cost is allocation when you need a modified version, which is rarely a problem in practice.
final on a field gives a visibility guarantee: after the constructor finishes, every thread sees the final fields correctly, even if the object was published through a data race.
Under the hood
The Java memory model treats final fields specially: a freeze action at the end of the constructor ensures that other threads that see the reference also see the initialised final fields.
Common mistakes
- Storing the caller's mutable list or array in a field without copying.
- Returning an internal collection from a getter.
- Calling a class immutable while it has a
finalfield that points to a mutable object.
How Unravel recognises it
When you paste code, Unravel looks for these signs of immutability and final classes. Spotting them yourself is a good habit when you read unfamiliar code:
- Annotations:
@Immutable@Value@ThreadSafe@NotThreadSafe - Method calls:
List.copyOf()Set.copyOf()Map.copyOf()Collections.unmodifiableList()Collections.unmodifiableMap()Collections.unmodifiableSet()Collections.unmodifiableCollection()toUnmodifiableList()toUnmodifiableSet()toUnmodifiableMap()Objects.requireNonNull() - Language constructs:
record
Part of a learning path
- Object-oriented Java in depth ยท previous: Initialisation order and init blocks ยท next: The Object class: toString, getClass, hashCode
Related concepts
Frequently asked questions
What is Immutability and final classes?
An immutable object never changes after construction. It can be shared freely between methods and threads without locks, and cannot be corrupted by a caller.
What are the most common mistakes with Immutability and final classes?
Storing the caller's mutable list or array in a field without copying. Returning an internal collection from a getter. Calling a class immutable while it has a final field that points to a mutable object.
How can I see Immutability and final classes in real code?
Paste your own Java into Unravel and it will point out where immutability and final classes appears, explain how it works at the detail level you choose, and draw the flow of the code around it.