Как реализовать геттер для коллекции в immutable классе в Java
Что проверяют
Понимаете ли вы разницу между неизменяемой ссылкой и неизменяемым содержимым, а также между неизменяемым представлением и копией. Следом спрашивают, что будет, если сами элементы коллекции изменяемые, и как это сделать в record.
Как отвечать
Защищаться нужно в двух местах, и главное из них конструктор:
- копия на входе:
this.items = List.copyOf(items). Метод появился в Java 10, возвращает неизменяемый список, бросаетNullPointerExceptionнаnull-элементах и не копирует повторно, если ему передали уже неизменяемый список изList.ofилиcopyOf; - геттер: после этого поле можно возвращать как есть. Список и так нельзя изменить, новая копия на каждый вызов только тратит память;
Collections.unmodifiableList: это обёртка-представление над исходным списком. Если обернуть переданный извне список без копии, его владелец продолжит менять содержимое, и изменения будут видны через обёртку. ПравильноunmodifiableList(new ArrayList<>(src)), этот вариант нужен, когда в списке допустимыnull;Stream.toList(): с Java 16 тоже возвращает неизменяемый список, удобно при сборке из потока;record: копию делают в компактном конструкторе,record Order(List<Item> items) { Order { items = List.copyOf(items); } }, и сгенерированный accessor отдаёт уже защищённое поле.
Упомяните остальные условия неизменяемости: класс final, поля private final, нет сеттеров, this не утекает из конструктора. И оговорку про глубину: неизменяемый список изменяемых объектов, например Date или DTO с сеттерами, класс неизменяемым не делает. Такие элементы копируют поштучно или заменяют неизменяемыми типами вроде LocalDate и records.
Где спотыкаются
Считают, что final на поле защищает коллекцию. Он запрещает только переприсвоить ссылку, а add и remove работают как прежде.
Оборачивают входной список в unmodifiableList без копии. Вызывающий код держит ссылку на оригинал и меняет состояние «неизменяемого» объекта.
Возвращают из геттера new ArrayList<>(items). Клиент вызывает add, думает, что изменил объект, и ошибка остаётся незамеченной, а каждый вызов геттера создаёт лишнюю копию.