Что такое иммутабельный (неизменяемый) объект?
Это значит, что объект создаётся один раз и навсегда остаётся в том же состоянии -- как photograph, который нельзя отредактировать.
Junior Level
Иммутабельный (неизменяемый) объект — это объект, состояние которого не меняется после создания. Все поля устанавливаются один раз в конструкторе, и после этого объект не предоставляет способов изменить свои данные.
Это значит, что объект создаётся один раз и навсегда остаётся в том же состоянии – как photograph, который нельзя отредактировать.
Простой пример
public final class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() { return x; }
public int getY() { return y; }
// Нет сеттеров — состояние нельзя изменить
}
Ключевые признаки
- Нет сеттеров (setX, setY и т.д.)
- Все поля —
private final - Класс —
final(нельзя наследоваться)
Когда НЕ использовать иммутабельные объекты
- High-allocation hot-path — миллионы изменений/сек, GC pressure
- Массивные структуры данных — копирование дороже мутации
- Прототипы и скрипты — скорость разработки важнее надёжности
Иммутабельный vs final поля vs без сеттеров
- Без сеттеров ≠ иммутабельный: можно менять через возвращённые ссылки на мутабельные поля
- Final поля ≠ иммутабельный: если поле ссылается на мутабельный объект, его можно изменить
- Иммутабельный: ни одно наблюдаемое состояние не меняется после конструирования
Middle Level
Пять правил создания иммутабельного класса
- Класс должен быть
final— подклассы не смогут добавить мутабельное состояние - Все поля
private final— доступ только через геттеры - Нет методов-мутаторов — никаких setX(), add(), remove()
- Защита мутабельных полей — если поле ссылается на изменяемый объект (например,
ListилиDate), нужно делать защитные копии - Безопасная публикация — не передавать
thisнаружу во время конструктора
Работа с коллекциями
public final class UserProfile {
private final String username;
private final List<String> roles;
public UserProfile(String username, List<String> roles) {
this.username = username;
this.roles = List.copyOf(roles); // защитная копия
// List.copyOf возвращает новый независимый список.
// Если передан null — будет NPE, поэтому добавьте null-check.
}
public List<String> getRoles() {
return roles; // List.copyOf уже вернул иммутабельный список
}
}
Современный подход — Records (Java 14+)
public record UserProfile(String username, List<String> roles) {
public UserProfile {
roles = List.copyOf(roles);
}
}
record автоматически делает класс final, поля private final, генерирует конструктор, equals(), hashCode(), toString().
Senior Level
Гарантии Java Memory Model (JMM)
Использование final полей обеспечивает безопасную публикацию (safe publication). Согласно спецификации JMM, после завершения конструктора иммутабельного объекта любой поток, получивший ссылку на этот объект, гарантированно увидит корректные значения его final полей. Это избавляет от необходимости использовать volatile или синхронизацию.
В конце конструктора происходит “заморозка” (freeze) всех final полей — создаётся memory barrier, предотвращающий переупорядочивание инструкций.
Производительность и Garbage Collection
Бытует мнение, что иммутабельность вредит производительности. Но в HotSpot JVM (основная реализация OpenJDK): аллокация в Young Generation крайне быстрая (Bump-the-pointer — просто сдвиг указателя). Другие JVM (Zing, GraalVM) могут отличаться:
- Короткоживущие объекты очищаются бесплатно при Minor GC
- Отсутствие Write Barriers (нет модификации Old Generation)
Опасность Constructor Escape
public ImmutableClass() {
ExternalService.register(this); // ОШИБКА! Объект опубликован до завершения инициализации
}
Другой поток может увидеть final поля в дефолтном состоянии (null или 0).
Deep Copy для вложенных мутабельных объектов
Если список содержит не String, а мутабельные User, List.copyOf недостаточен — нужно клонировать каждый элемент.
Резюме для Senior
- Иммутабельность — это “контракт безопасности”
finalна уровне класса и полей — обязательное требование JMM- Всегда делайте копии мутабельных компонентов
- Используйте Records для сокращения бойлерплейта
🎯 Шпаргалка для интервью
Обязательно знать:
- Иммутабельный объект — состояние не меняется после создания
- Три ключевых признака:
finalкласс,private finalполя, отсутствие сеттеров - Пять правил: final class, private final поля, нет мутаторов, защита мутабельных полей, безопасная публикация
List.copyOf()/Collections.unmodifiableList()для защиты коллекций- Records (Java 14+) автоматически генерируют иммутабельный класс
- JMM гарантирует safe publication для
finalполей после конструктора - Иммутабельность ≠ thread-safe для ссылок на мутабельные объекты
- Constructor Escape — критическая ошибка при публикации
thisдо завершения конструктора
Частые уточняющие вопросы:
- Достаточно ли final полей? — Нет, нужна ещё защита мутабельных полей и
finalкласс - Что такое deep copy? — Рекурсивное копирование всех вложенных мутабельных объектов
- Когда НЕ использовать иммутабельность? — High-allocation hot-path, массивные структуры, прототипы
- Records vs обычный класс? — Records: минимум boilerplate, но нет наследования и extra-полей
Красные флаги (НЕ говорить):
- «Иммутабельный = просто без сеттеров» — мутабельные поля всё ещё можно менять через геттеры
- «Final полей достаточно» — без defensive copy мутабельные объекты уязвимы
- «Иммутабельность всегда медленнее» — в многопоточной среде без блокировок она быстрее
- «Можно менять иммутабельный объект через反射» — в Java 17+ это заблокировано
Связанные темы:
- [[2. Какие преимущества даёт использование иммутабельных объектов]]
- [[3. Как создать иммутабельный класс в Java]]
- [[7. Что такое ключевое слово final и как оно помогает в создании иммутабельных классов]]
- [[8. Достаточно ли сделать все поля final для иммутабельности]]
- [[20. Что такое Record и как он помогает создавать иммутабельные классы]]