Конструктор сохранил переданный List по ссылке (this.items = items). Почему объект всё равно изменяем?
Поле хранит чужую ссылку: вызывающий меняет список снаружи, и «неизменяемый» объект меняется. final запрещает переприсвоить поле, но не менять содержимое. Нужна защитная копия на входе (List.copyOf) и неизменяемое представление на выходе.
- Копировать до валидации — иначе гонка «проверил, а потом подменили»
- Collections.unmodifiableList без копии не защищает от исходной ссылки
- Дата: java.util.Date изменяема, LocalDate — нет
А ещё List.copyOf запрещает null-элементы
Подробно
final у ссылки запрещает переприсвоить поле, но не делает сам список неизменяемым. Если сохранить переданную ссылку, у вызывающего остаётся та же ссылка, и он поменяет «внутренности» вашего объекта — неизменяемость иллюзорна. Поэтому изменяемые входные данные копируют в конструкторе (List.copyOf(events)), а в геттере возвращают копию или неизменяемое представление. Это defensive copy.
Что хотят услышать
- Почему final недостаточно.
- Копии на входе и выходе.
Частые ошибки
- Думать, что final-ссылка делает содержимое неизменяемым.
- Возвращать внутренний список из геттера напрямую.
Проверьте себя
Один верный вариант. Подумайте, потом откройте ответ.
Показать варианты и ответ
- ✓Вызывающий меняет тот же список снаружи — нужна защитная копия на входе
- ✗Достаточно пометить поле final — тогда и содержимое списка станет неизменяемым
- ✗List нельзя хранить в final-поле, поэтому неизменяемость ломается
- ✗Проблема возникает, только если список пустой
Что спросят дальше
- Что делает List.copyOf на входе?
- Почему копию нужно делать и в геттере?
Разбор темы
- Проектирование неизменяемых классов
Зачем нужна неизменяемость, рецепт immutable-класса, защитные копии, безопасная публикация и records.
Связанные вопросы
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.