Ответ
Создание иммутабельного класса с полем-коллекцией требует защитных копий (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). - Геттер: Возвращая неизменяемое представление, мы предотвращаем модификацию через полученную ссылку.
Для модифицируемых объектов внутри коллекции требуется глубокая неизменяемость — элементы коллекции также должны быть иммутабельными, либо при копировании нужно создавать копии и этих объектов.
Ответ 18+ 🔞
А, ну это же классика, блядь! Сейчас объясню на пальцах, как сделать этот ваш иммутабельный класс, чтобы он был крепче, чем совесть у чиновника. Сиди, слушай.
Значит, смотри. Есть у тебя класс, и ты хочешь, чтобы он был как скала — чтобы никакой внешний код не мог его пошатнуть, изменить его внутренности. Особенно если там внутри какая-нибудь коллекция сидит, типа списка телефонов. Это же лазейка, блядь! Дали тыщу раз на это ебало!
Вот основные правила, без которых нихуя не получится:
- Класс в
final. Чтобы какой-нибудь умник не наследовался и не накостылял тебе бяку в наследничке. Нахуй наследников, мы тут сами с усами. - Поля все
private final.Private— чтобы с улицы не дотянулись,final— чтобы один раз присвоили и забыли, как страшный сон. - Никаких сеттеров! Вообще, нихуя. Забыл это слово. Если захочешь "изменить" объект — создавай новый, с новыми данными. Это и есть иммутабельность, ёпта.
- А теперь самое важное, блядь — защитные копии! Это как презерватив для твоего класса. Надеваешь в двух местах:
- В конструкторе: Когда тебе передают коллекцию извне, ты не должен тупо сохранять ссылку на неё. А то придут эти пидарасы, которые тебе коллекцию передали, и потом её нахуй изменят, а твой объект, как лох, будет смотреть на эти изменения. Надо создать НОВУЮ коллекцию и скопировать в неё все элементы.
new ArrayList<>(входящаяКоллекция). Вот так, в рот меня чих-пых! - В геттере: Когда ты отдаёшь свою коллекцию наружу, нельзя отдавать прямую ссылку на внутренний список. Отдашь — и опять придут, начнут там add/remove делать. Надо либо новую копию вернуть (
return new ArrayList<>(internalList)), либо, что чаще и правильнее, — неизменяемое представление.return Collections.unmodifiableList(internalList). Попробуй изменить такую штуку — получишьUnsupportedOperationExceptionпрямо в ебальник.
- В конструкторе: Когда тебе передают коллекцию извне, ты не должен тупо сохранять ссылку на неё. А то придут эти пидарасы, которые тебе коллекцию передали, и потом её нахуй изменят, а твой объект, как лох, будет смотреть на эти изменения. Надо создать НОВУЮ коллекцию и скопировать в неё все элементы.
Вот, смотри, как это выглядит в коде, сука:
import java.util.*;
public final class ImmutablePerson { // Правило 1: final класс
private final String name; // Правило 2: private final
private final List<String> phoneNumbers; // Наше поле-коллекция
// КОНСТРУКТОР. Надеваем первый презерватив.
public ImmutablePerson(String name, Collection<String> phoneNumbers) {
this.name = name;
// ВАЖНО! Не this.phoneNumbers = phoneNumbers; // ПИЗДЕЦ ТАК ДЕЛАТЬ!
// А вот так:
// 1. Создаём копию переданного (new ArrayList<>).
// 2. И сразу оборачиваем в бронежилет (unmodifiableList).
this.phoneNumbers = Collections.unmodifiableList(
new ArrayList<>(phoneNumbers) // Защитное копирование!
);
}
public String getName() {
return name; // Со строкой всё просто, она и так иммутабельна
}
// ГЕТТЕР. Надеваем второй презерватив.
public List<String> getPhoneNumbers() {
// Возвращаем наше неизменяемое представление.
// Те, кто получит эту ссылку, смогут только смотреть.
return phoneNumbers; // Это уже unmodifiableList, можно так.
// Или return new ArrayList<>(phoneNumbers); // Альтернатива — вернуть копию.
}
}
И последнее предупреждение, блядь, самое важное! Всё это работает на ура, только если элементы внутри коллекции сами по себе иммутабельны (как строки String в нашем примере). Если бы там были какие-нибудь свои объекты, которые можно менять, то хоть оберни коллекцию в титановый ящик — эти объекты изнутри всё равно можно будет изменить. Это называется глубокая неизменяемость, и её надо продумывать отдельно, либо копируя и сами объекты.
Короче, запомни: создавая иммутабельный класс с коллекцией, ты как сапёр — дважды проверяешь, откуда данные пришли и куда уходят. И тогда всё будет пиздато.