Immutable Classes
TL;DR
- An Immutable Class is a class whose instances cannot be modified after they are created.
- Example:
StringandIntegerare immutable. - To create one: Make the class
final, fieldsprivate final, provide no setters, and deeply clone mutable objects in getters.
Concept
Immutable objects are inherently thread-safe because multiple threads cannot change their state. They are also excellent candidates for keys in a HashMap because their hash code will never change.
Steps to create an Immutable Class:
- Declare the class as
finalso it can’t be extended (child classes could override methods to mutate state). - Make all fields
privateandfinal. - Initialize all fields via a constructor.
- Do not provide any
settermethods. - Crucial: If the class contains mutable objects (like
DateorList), return a deep copy or clone of them in the getter, rather than the original reference.
Examples
import java.util.ArrayList;
import java.util.List;
// 1. Class is final
public final class ImmutableStudent {
// 2. Fields are private and final
private final String name;
private final List<String> courses;
// 3. Initialized in constructor
public ImmutableStudent(String name, List<String> courses) {
this.name = name;
// MUST deep copy mutable objects passed into constructor
this.courses = new ArrayList<>(courses);
}
// 4. No Setters! Only Getters.
public String getName() {
return name;
}
public List<String> getCourses() {
// 5. MUST return a copy of the mutable object, not the original reference
return new ArrayList<>(courses);
}
}
Interview Questions
Q: Why must we return a copy of a List in the getter of an immutable class?
A: If you simply return this.courses;, you are returning the memory reference to the list. The caller could then do student.getCourses().add("Hacking");. Even though your class has no setters and the field is final, the contents of the list would be mutated, breaking immutability. Returning a new copy prevents this.
Q: Are Java Records immutable?
A: Yes. Introduced in Java 14, record is a special class type designed to be an immutable data carrier. All fields in a record are implicitly private final, and it automatically generates getters, a constructor, equals, hashCode, and toString. However, if a record holds a mutable object (like an array or List), you still need to be careful with deep copying.