Получить доступ

Проектирование неизменяемых классов

Зачем нужна неизменяемость, рецепт immutable-класса, защитные копии, безопасная публикация и records.

Что даёт неизменяемость

Неизменяемый (immutable) объект — тот, чьё наблюдаемое состояние не меняется после создания. Это не самоцель, а мощный инструмент простоты и безопасности.

Плюсы: потокобезопасность без всякой синхронизации (нечего портить гонкой), безопасность как ключей Map и элементов Set (хеш не «уедет»), свободное кэширование и шаринг одного экземпляра, и просто более лёгкие рассуждения о коде — значение нельзя поменять «из-под ног». Поэтому общий совет — делать классы неизменяемыми по умолчанию, а изменяемость вводить только при необходимости.

Рецепт immutable-класса

Чтобы класс был по-настоящему неизменяемым: (1) не давать сеттеров и иных мутаторов; (2) все поля сделать private final; (3) запретить наследование (класс final или приватный конструктор + фабрики), иначе подкласс может добавить изменяемость; (4) если поля ссылаются на изменяемые объекты — делать защитные копии на входе и выходе; (5) не давать ссылке this утечь из конструктора (см. безопасную публикацию).

Пункты 1–3 очевидны, а 4 — самый забываемый и важный.

Защитные копии

Если конструктор просто сохранит переданную ссылку на изменяемый объект (List, массив, Date), вызывающий сохранит свою копию ссылки и сможет поменять «внутренности» вашего объекта снаружи — неизменяемость иллюзорна. Поэтому изменяемые входные данные копируют в конструкторе, а в геттере возвращают копию или неизменяемое представление.

Это и есть defensive copy — базовый приём дизайна immutable-классов и защиты инвариантов вообще.

final class Period {
    private final List<Event> events;
    Period(List<Event> events) {
        this.events = List.copyOf(events);   // копия на входе — снаружи не подменят
    }
    List<Event> events() { return events; } // уже неизменяемый список
}

Безопасная публикация

Неизменяемость и многопоточность связаны напрямую: объект, у которого ВСЕ поля final и ссылка this не утекла из конструктора, по модели памяти Java безопасно публикуется — любой поток, получивший ссылку на готовый объект, увидит его поля корректно установленными без синхронизации (final field freeze).

Именно поэтому «immutable ⇒ потокобезопасен». Подробно про этот механизм — в уроке про JMM.

Стоимость и приёмы

Цена неизменяемости — копирование при «изменении»: вместо мутации создают новый объект (методы withX, возвращающие копию с одним изменённым полем). Для редких изменений это дёшево; для частых — используют изменяемый Builder, который собирает состояние, а на выходе отдаёт неизменяемый результат, или специализированные persistent-структуры.

Главное — не превращать «withX на каждый чих» в источник мусора в горячем цикле; там уместен мутабельный буфер с финальной заморозкой.

records

Современный лаконичный способ объявить неизменяемый носитель данных — record (см. урок про современную Java): он автоматически делает компоненты private final, генерирует конструктор, геттеры, equals/hashCode и toString.

Важная оговорка: record гарантирует неизменяемость только своих ССЫЛОК, но не делает защитных копий за вас. Если компонент — изменяемый List, его всё равно нужно копировать в компактном конструкторе, иначе «неизменяемая» запись протекает.

Как отвечать на собеседовании

Назовите выгоды (потокобезопасность, безопасные ключи, кэшируемость) и рецепт (final-класс, private final поля, без сеттеров, защитные копии, не публиковать this). Подчеркните defensive copy на входе/выходе как самый забываемый пункт, свяжите immutable с безопасной публикацией (все поля final → потокобезопасен) и оговорите, что record не делает защитных копий сам.

Вопросы по теме

Читайте также

Закрытая бета

Знать тему — половина дела

На живом собеседовании sotto слышит вопрос интервьюера и показывает короткий ответ и план. Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем