Как создать неизменяемый класс
Короткий ответ
Запись (record) — самый короткий способ. Её поля финальные, а конструктор, методы доступа, equals(), hashCode() и toString() создаются автоматически:
record Money(String currency, long cents) {}
void main() {
var price = new Money("EUR", 1999);
IO.println(price);
IO.println(price.cents());
}Результат:
Money[currency=EUR, cents=1999]
1999Проверка в конструкторе
Компактный конструктор записи проверяет значения до присвоения полям:
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);
}Результат:
Money[currency=EUR, cents=500]
Exception in thread "main" java.lang.IllegalArgumentException: cents must not be negative: -1Изменение — это новый объект
Вместо сеттеров добавьте методы, которые возвращают новый объект:
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);
}Результат:
Money[currency=EUR, cents=1000]
Money[currency=EUR, cents=1250]Коллекции внутри
Запись хранит ссылку на переданный список. Сделайте неизменяемую копию в конструкторе:
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());
}Результат:
[Anna]Обычный класс
Без записи: класс final, все поля private final, нет сеттеров:
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());
}Результат:
3, 4Частая ошибка
Хранить изменяемый список без копии. Внешний код меняет «неизменяемый» объект:
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());
}Результат:
[Anna, Oleg]Функции из этого ответа
- record — Объявляет запись — неизменяемый класс для хранения данных с готовыми equals(), hashCode() и toString().
- final — Запрещает менять переменную после присваивания, переопределять метод или наследовать класс.
- List.copyOf() — Создаёт неизменяемую копию коллекции в виде списка.