How to create an immutable class
Short answer
A record is the shortest way. Its fields are final, and the constructor, accessors, equals(), hashCode() and toString() are generated automatically:
record Money(String currency, long cents) {}
void main() {
var price = new Money("EUR", 1999);
IO.println(price);
IO.println(price.cents());
}Output:
Money[currency=EUR, cents=1999]
1999Validation in the constructor
A record's compact constructor checks the values before they are assigned to the fields:
record Money(String currency, long cents) {
Money {
if (cents < 0) {
throw new IllegalArgumentException("cents must not be negative: " + cents);
}
}
}
void main() {
IO.println(new Money("EUR", 500));
new Money("EUR", -1);
}Output:
Money[currency=EUR, cents=500]
Exception in thread "main" java.lang.IllegalArgumentException: cents must not be negative: -1A change means a new object
Instead of setters, add methods that return a new object:
record Money(String currency, long cents) {
Money plus(long more) {
return new Money(currency, cents + more);
}
}
void main() {
var price = new Money("EUR", 1000);
var total = price.plus(250);
IO.println(price);
IO.println(total);
}Output:
Money[currency=EUR, cents=1000]
Money[currency=EUR, cents=1250]Collections inside
A record stores a reference to the list it was given. Make an unmodifiable copy in the constructor:
record Team(String name, List<String> members) {
Team {
members = List.copyOf(members);
}
}
void main() {
var people = new ArrayList<>(List.of("Anna"));
var team = new Team("Blue", people);
people.add("Oleg");
IO.println(team.members());
}Output:
[Anna]A regular class
Without a record: the class is final, all fields are private final, and there are no setters:
final class Point {
private final int x;
private final int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
int x() { return x; }
int y() { return y; }
}
void main() {
var p = new Point(3, 4);
IO.println(p.x() + ", " + p.y());
}Output:
3, 4Common mistake
Storing a mutable list without a copy. Outside code changes the "immutable" object:
record Team(String name, List<String> members) {}
void main() {
var people = new ArrayList<>(List.of("Anna"));
var team = new Team("Blue", people);
people.add("Oleg");
IO.println(team.members());
}Output:
[Anna, Oleg]Functions used in this recipe
- record — Declares a record — an immutable data class with ready-made equals(), hashCode() and toString().
- final — Forbids reassigning a variable, overriding a method or extending a class.
- List.copyOf() — Creates an unmodifiable list copy of a collection.