Ответ
Создание иммутабельного класса с полем-коллекцией требует защитных копий (defensive copying) на этапах инициализации и возврата значения, чтобы внешний код не мог изменить внутреннее состояние объекта.
Ключевые шаги:
- Объявите класс как
final, чтобы запретить наследование. - Объявите все поля как
private final. - Не предоставляйте сеттеров (mutators).
- В конструкторе создавайте глубокую защитную копию переданной коллекции.
- В геттере возвращайте неизменяемое представление (unmodifiable view) или копию коллекции.
Пример реализации:
import java.util.*;
public final class ImmutablePerson {
private final String name;
private final List<String> phoneNumbers; // Поле-коллекция
// Конструктор с защитным копированием
public ImmutablePerson(String name, Collection<String> phoneNumbers) {
this.name = name;
// 1. Создаем новый список на основе переданной коллекции
// 2. Оборачиваем его в unmodifiableList
this.phoneNumbers = Collections.unmodifiableList(
new ArrayList<>(phoneNumbers)
);
}
public String getName() {
return name;
}
// Геттер возвращает неизменяемое представление
public List<String> getPhoneNumbers() {
// Возвращаем само unmodifiableList поле.
// Альтернатива: вернуть новую копию -> return new ArrayList<>(phoneNumbers);
return phoneNumbers;
}
}
Почему это работает и важно:
- Конструктор:
new ArrayList<>(phoneNumbers)создает копию. Даже если переданная коллекция изменится после создания объекта, внутреннее состояниеImmutablePersonостанется прежним. - Поле:
Collections.unmodifiableList(...)гарантирует, что даже ссылка на внутренний список не позволит его модифицировать (при попытке выброситUnsupportedOperationException). - Геттер: Возвращая неизменяемое представление, мы предотвращаем модификацию через полученную ссылку.
Для модифицируемых объектов внутри коллекции требуется глубокая неизменяемость — элементы коллекции также должны быть иммутабельными, либо при копировании нужно создавать копии и этих объектов.