Как создать иммутабельный класс в Java?
Чтобы создать иммутабельный класс, следуйте этим шагам:
Junior Level
Чтобы создать иммутабельный класс, следуйте этим шагам:
Шаг 1: Объявите класс как final
public final class Person {
Шаг 2: Сделайте все поля private final
private final String name;
private final int age;
Шаг 3: Не создавайте сеттеры
Только геттеры (методы для чтения):
public String getName() { return name; }
public int getAge() { return age; }
Шаг 4: Инициализируйте поля в конструкторе
public Person(String name, int age) {
this.name = name;
this.age = age;
}
Полный пример
public final class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
}
Когда НЕ создавать иммутабельный класс
- JPA-entities — обычно мутабельны (ORM требует setters, no-arg constructor)
- Простые скрипты/прототипы — скорость разработки важнее
- DTO для сериализации — часто удобнее mutable с setters
Middle Level
Работа с мутабельными полями
Если класс содержит коллекции или другие изменяемые объекты, нужно делать защитные копии:
public final class ImmutableReport {
private final String title;
private final List<String> items;
public ImmutableReport(String title, List<String> items) {
this.title = title;
this.items = (items == null) ? List.of() : List.copyOf(items);
}
public String getTitle() { return title; }
public List<String> getItems() {
return items; // List.copyOf уже вернул иммутабельный список
}
}
Реализация через Record (Java 14+)
public record ImmutableReport(String title, List<String> items) {
public ImmutableReport {
items = List.copyOf(items); // компактный конструктор
}
}
record автоматически:
- Делает класс
final - Делает поля
private final - Генерирует конструктор,
equals(),hashCode(),toString()
Пять правил Джошуа Блоха
- Не предоставляйте методы-мутаторы
- Обеспечьте невозможность расширения класса (
final) - Сделайте все поля
final - Сделайте все поля
private - Обеспечьте эксклюзивный доступ к мутабельным компонентам
Senior Level
Опасность утечки “this” (Constructor Escape)
public ImmutableClass() {
ExternalService.register(this); // ОШИБКА!
}
Если другой поток обратится к объекту до завершения конструктора, он увидит final поля в дефолтном состоянии.
Глубокое копирование (Deep Copy)
Если список содержит мутабельные объекты, List.copyOf недостаточен:
public record Order(Long id, List<Item> items) {
public Order {
items = items.stream()
.map(original -> new Item(original)) // Вместо clone() (который считается broken — Effective Java Item 13): конструктор копирования — надёжнее
.toList();
}
}
Reflection атака
Начиная с Java 9 (Project Jigsaw) и особенно с Java 16+ (JEP 396) доступ через reflection к java.base полям заблокирован по умолчанию.
Резюме для Senior
- Обычного
finalнедостаточно для мутабельных ссылочных типов - Всегда используйте
List.copyOf()илиCollections.unmodifiableList() - Помните про Constructor Escape
- Для простых DTO выбирайте Records
- Глубокая иммутабельность требует копирования всей иерархии объектов
🎯 Шпаргалка для интервью
Обязательно знать:
- 4 шага: final class, private final поля, нет сеттеров, инициализация в конструкторе
- Пять правил Джошуа Блоха: нет мутаторов, нет расширения, final поля, private поля, защита мутабельных компонентов
- Для мутабельных полей —
List.copyOf()в конструкторе и возврат иммутабельной обёртки из геттера - Records (Java 14+) автоматически делают класс final, поля private final, генерируют equals/hashCode/toString
- Constructor Escape — нельзя передавать
thisнаружу во время конструктора - Deep Copy: если список содержит мутабельные объекты, нужно клонировать каждый элемент
- Когда НЕ создавать: JPA-entities, простые прототипы, DTO для сериализации
Частые уточняющие вопросы:
- Что делать с коллекциями в конструкторе? —
List.copyOf(input)создаёт независимую копию - Records vs обычный класс? — Records: минимум кода, но нет наследования; обычный класс: полный контроль
- Что такое Constructor Escape? — Публикация
thisдо завершения конструктора = другой поток увидит null-поля - Достаточно ли List.copyOf для глубокой иммутабельности? — Нет, если элементы списка мутабельны
Красные флаги (НЕ говорить):
- «Просто final полей — и всё» — без defensive copy мутабельные поля уязвимы
- «Можно передать this в конструкторе для регистрации» — это Constructor Escape
- «List.copyOf копирует элементы» — копируется только контейнер, элементы те же
- «Record решает все проблемы» — коллекции в Records тоже нужно копировать вручную
Связанные темы:
- [[1. Что такое иммутабельный (неизменяемый) объект]]
- [[7. Что такое ключевое слово final и как оно помогает в создании иммутабельных классов]]
- [[8. Достаточно ли сделать все поля final для иммутабельности]]
- [[9. Что делать, если поле класса ссылается на мутабельный объект]]
- [[20. Что такое Record и как он помогает создавать иммутабельные классы]]