Week 1 Day 4 — final and Immutability
Goal
Today I want final and immutability as two different ideas that interviews often mash together.
Main questions:
- What does
finalmean on a variable, method, and class? - What does immutable mean?
- How do I make a class immutable?
- Why is
Stringimmutable? - Why do Spring beans still need care even with
finalfields?
1. What final actually does
| Place | Meaning |
|---|---|
| Local / parameter / field | The binding cannot change after assignment. |
| Method | Subclasses cannot override it. |
| Class | Nobody can extend it. |
final int n = 3;
// n = 4; // compile error
final List<String> names = new ArrayList<>();
names.add("Ada"); // allowed: the list object mutates
// names = List.of("Bob"); // compile error: new binding
final on a reference is a constant pointer, not a frozen object.
Memory sentence:
finalstops reassignment. It does not stop mutation of the object.
2. Immutability
An immutable object cannot change after construction, through any reference.
That is stronger than final fields:
- fields are
private final - no setters
- no methods that mutate
- mutable inputs are copied in
- mutable internals are copied out (or never exposed)
- the class is
final(or sealed) so a subclass cannot add mutation
public final class Money {
private final String currency;
private final long cents;
public Money(String currency, long cents) {
this.currency = currency;
this.cents = cents;
}
public Money plus(Money other) {
if (!currency.equals(other.currency)) {
throw new IllegalArgumentException("currency");
}
return new Money(currency, cents + other.cents);
}
}
plus returns a new object. The original is unchanged. Several threads can share Money without locks.
3. Defensive copies
final on a List field is not enough.
public final class Team {
private final List<String> members;
public Team(List<String> members) {
this.members = members; // leak: caller can still mutate
}
public List<String> members() {
return members; // leak: caller can still mutate
}
}
Fix:
this.members = List.copyOf(members); // copy in, unmodifiable
return members; // safe if members is unmodifiable
// or return List.copyOf(members);
A record with a List component has the same leak unless I copy in a compact constructor. Records are shallowly immutable: the fields cannot be reassigned, nested objects can still change.
4. Why String is immutable
String is a final class with private state and no mutators. Methods like toUpperCase return new strings.
Why the language designers wanted that:
- several references can share one
Stringsafely - it is a safe
HashMapkey: the hash cannot change - the string pool interned literals are shareable
- no locking is needed to read a string
String a = "ada";
String b = a.toUpperCase();
// a is still "ada"
Memory sentence:
Immutable values are shareable. Mutable values need a clear owner.
5. Spring connection
Constructor injection with private final collaborators is the default I want:
@Service
public class OrderService {
private final OrderRepository orders;
public OrderService(OrderRepository orders) {
this.orders = orders;
}
}
The field binding is immutable. The OrderService instance is still a shared singleton. If OrderRepository is a Spring proxy with state, or if I add a mutable List<Order> field, I am back to shared mutation.
Judgment:
- Collaborators:
final, injected, no setters. - Request data: method parameters, never singleton fields.
- Domain values (
Money, IDs, commands): prefer immutable types. - Entities: mutable by JPA’s design. Do not treat them as value objects.
6. Common traps
Trap 1: “final means immutable.”
Only the binding is fixed.
Trap 2: Immutable class that returns its internal Date or ArrayList.
Callers mutate through the leaked reference.
Trap 3: Using StringBuilder as if it were a String.
StringBuilder is a mutable buffer. It is the right tool for building text in a loop, not for sharing.
Trap 4: Making a Spring @Service “immutable” and then storing request state in a field.
The type looks clean. The singleton still shares that field across requests.
Practice Questions and Answers
Question 1
What does final mean on a field of type List?
Answer:
I cannot point the field at a different list after assignment. I can still add or remove elements if the list implementation is mutable.
Question 2
How do I make a class immutable?
Answer:
Make the class final, fields private final, no setters, set all state in the constructor, copy mutable inputs, never leak mutable internals. Methods that would “change” state return a new instance.
Question 3
Why are immutable objects useful as map keys?
Answer:
A HashMap places the key in a bucket from hashCode, then finds it with equals. If the key mutates, the hash no longer matches the bucket, and the entry is lost. Immutable keys cannot move.
Question 4
Is a record always immutable?
Answer:
The record’s component bindings cannot be reassigned. If a component is a mutable type (ArrayList, array, bean), the record is only shallowly immutable. Copy in the constructor if I need a real value object.
Question 5
Why do we still use final on Spring service fields?
Answer:
It documents that collaborators are assigned once, enables constructor injection, and prevents accidental reassignment. It does not make the service thread-safe by itself. Thread-safety still depends on not storing request state and on what the collaborators do.
Memory sentences
finalstops reassignment; immutability also stops nested mutation.
Share values. Give mutable objects a single owner.
private finalcollaborators are the Spring default, not a thread-safety proof.