Как сделать объект иммутабельным в Java?

Ответ

Чтобы сделать объект иммутабельным (неизменяемым), необходимо следовать набору строгих правил при проектировании его класса. Это гарантирует, что состояние объекта останется постоянным после создания.

Пошаговый алгоритм:

  1. final класс: Объявите класс с модификатором final, чтобы предотвратить создание подклассов, которые могут нарушить иммутабельность.
  2. private final поля: Все поля должны быть объявлены как private и final. Это обеспечивает:
    • Инкапсуляцию (прямой доступ запрещен).
    • Однократную инициализацию (значение присваивается только в конструкторе).
  3. Отсутствие сеттеров: Не предоставляйте методов, изменяющих состояние полей.
  4. Безопасная инициализация в конструкторе:
    • Для примитивов и immutable-типов (например, String, Integer) — присваивайте напрямую.
    • Для mutable-объектов и коллекций — создавайте защитные копии (defensive copy).
  5. Безопасное возвращение значений из геттеров:
    • Для mutable-полей возвращайте глубокую копию объекта.
    • Для коллекций возвращайте неизменяемое представление (например, через Collections.unmodifiableList()) или копию.

Пример с коллекцией:

import java.util.*;

public final class ImmutableTeam {
    private final String name;
    private final List<String> members;

    public ImmutableTeam(String name, List<String> members) {
        this.name = name;
        // 1. Защитная копия при инициализации
        this.members = new ArrayList<>(members);
    }

    public String getName() { return name; }

    public List<String> getMembers() {
        // 2. Возвращаем неизменяемое представление внутренней коллекции
        return Collections.unmodifiableList(members);
    }
}
// Использование
List<String> originalList = new ArrayList<>(Arrays.asList("Alice", "Bob"));
ImmutableTeam team = new ImmutableTeam("Devs", originalList);
originalList.add("Charlie"); // Не влияет на team.members
team.getMembers().add("David"); // Выбросит UnsupportedOperationException

Соблюдение этих правил делает объект потокобезопасным по умолчанию и упрощает рассуждения о коде.

Ответ 18+ 🔞

А, ну вот, смотри, опять эти ваши иммутабельные объекты, блядь. Ну, типа, чтобы твой класс был как гранитная глыба — создали и нихуя с ним не сделаешь. Чтоб не было этих сюрпризов, когда где-то в коде кто-то твою коллекцию нахуй переписал, а ты потом охуеешь. Так вот, правила, блядь, железные.

Как сделать, чтобы объект был как скала, ёпта:

  1. Класс — final. Это чтоб какой-нибудь умник не наследовался от тебя и не наворотил в подклассе какую-нибудь хуйню, которая всё сломает. Запечатал нахуй, и всё.
  2. Поля — private final. Private — чтобы с улицы не лапали, final — чтобы один раз в конструкторе присвоил и забыл, как страшный сон. Менять? Не, не слышал.
  3. Сеттеры? На хуй сеттеры! Вообще нихуя методов, которые меняют состояние. Только читать можно.
  4. Конструктор — чтоб не подсунули свинью.
    • Примитивы да String'и — похуй, присваивай.
    • А вот если тебе в конструктор передают какой-нибудь List или свой кастомный объект — тут, сука, внимание! Надо делать защитную копию (defensive copy). А то передадут тебе ссылку на свой список, ты её в своё final-поле запишешь, а они потом у себя этот список наизнанку вывернут — и твой "иммутабельный" объект, блядь, тоже поменяется! Пиздец и скандал. Копируй нахуй сразу.
  5. Геттеры — тоже с подвохом.
    • Возвращаешь поле-коллекцию? Так нельзя, ёба! Вернёшь ссылку на внутренний список — и опять какой-нибудь пидор его изменит. Поэтому:
      • Либо возвращай глубокую копию всего этого добра.
      • Либо (для коллекций) — возвращай неизменяемое представление, обёртку, через которую нихуя не добавишь и не удалишь.

Смотри, как это в коде выглядит, на примере команды:

import java.util.*;

public final class ImmutableTeam {
    private final String name;
    private final List<String> members; // Список, блядь, mutable!

    public ImmutableTeam(String name, List<String> members) {
        this.name = name;
        // ВАЖНО! Не this.members = members; // Это пиздец, ловушка!
        // А делаем ЗАЩИТНУЮ КОПИЮ при создании:
        this.members = new ArrayList<>(members);
    }

    public String getName() { return name; }

    public List<String> getMembers() {
        // ВАЖНО! Не return members; // Это опять пиздец, дадим доступ на изменение!
        // А возвращаем НЕИЗМЕНЯЕМУЮ ОБЁРТКУ:
        return Collections.unmodifiableList(members);
    }
}
// Проверяем, что всё работает как надо
List<String> originalList = new ArrayList<>(Arrays.asList("Алиса", "Боб"));
ImmutableTeam team = new ImmutableTeam("Разработчики", originalList);
originalList.add("Чарли"); // Ха-ха, сука! На team.members это НЕ повлияло!
team.getMembers().add("Давид"); // А тут, блядь, вылетит UnsupportedOperationException! Нельзя изменять!

Вот так, ебушки-воробушки. Сделал по этим правилам — и спи спокойно. Объект твой теперь потокобезопасен по умолчанию (потому что менять нечего) и логику проще отслеживать — создался и застыл, как истукан.