Как создать иммутабельный (неизменяемый) класс в Java?

Ответ

Иммутабельный класс — это класс, экземпляры которого нельзя изменить после создания. Это ключевая концепция для написания потокобезопасного и предсказуемого кода.

Правила создания:

  1. Объявите класс как final — запрещает наследование и переопределение методов.
  2. Сделайте все поля private и final — гарантирует однократную инициализацию и скрывает состояние.
  3. Не предоставляйте сеттеров (setter methods).
  4. Инициализируйте все поля через конструктор.
  5. Защитите от "утечки ссылок" (defensive copying):
    • Для mutable-полей в конструкторе сохраняйте копию переданного объекта.
    • Для геттеров, возвращающих mutable-поля, возвращайте копию или неизменяемое представление.

Пример:

import java.util.*;

public final class ImmutablePerson {
    private final String name;
    private final Date birthDate; // mutable класс!
    private final List<String> hobbies;

    public ImmutablePerson(String name, Date birthDate, List<String> hobbies) {
        this.name = name;
        // Защитная копия для mutable-объекта
        this.birthDate = new Date(birthDate.getTime());
        // Защитная копия для коллекции
        this.hobbies = new ArrayList<>(hobbies);
    }

    public String getName() { return name; }

    public Date getBirthDate() {
        // Возвращаем копию, а не оригинальную ссылку
        return new Date(birthDate.getTime());
    }

    public List<String> getHobbies() {
        // Возвращаем неизменяемую обертку
        return Collections.unmodifiableList(new ArrayList<>(hobbies));
    }
}

Для удобного создания объектов со многими полями используйте паттерн Builder.

Ответ 18+ 🔞

Да ты посмотри на эту штуку — иммутабельный класс! Это ж как каменная баба, блядь, которую после установки уже ни сдвинуть, ни перекрасить. Создал — и спи спокойно, ни одна поехавшая нить исполнения тебе его не испоганит. Идеально для потоков, эти обоссанцы друг другу мозги не вынесут.

Вот как эту крепость строить, чтобы не развалилась:

  1. Класс в final и под замок, ёпта! Чтобы никакой умник-наследник не приперся со своими кривыми переопределениями и всё не раздолбал.
  2. Поля — private, final и ни шага назад! Инициализировал один раз в конструкторе — и всё, приехали. Никаких задних дверей.
  3. Сеттеры? На хуй сеттеры! Кто это придумал — менять что попало? Тут состояние раз и навсегда, как приговор.
  4. Всё загружаем через конструктор. Это наш главный и единственный пропускной пункт.
  5. А вот тут внимание, блядь, самый подлый подводный камень! Защита от утечки ссылок, или defensive copying. Если тебе в конструктор суют какой-нибудь изменяемый объект вроде Date или List — ты не ссылку на него сохраняй, а его копию, новенькую! И когда геттером это наружу выдаёшь — тоже копию или неизменяемую обёртку. А то придут эти пидарасы, возьмут твой «неизменяемый» объект, через геттер достанут список хобби и начнут там хуйню добавлять. И всё, пиздец твоей иммутабельности, все потоки в ахуе.

Смотри, как это выглядит в коде, на примере какого-нибудь «НесокрушимогоЧеловека»:

import java.util.*;

public final class ImmutablePerson {
    private final String name;
    private final Date birthDate; // А этот, сука, mutable! Коварная жопа!
    private final List<String> hobbies;

    public ImmutablePerson(String name, Date birthDate, List<String> hobbies) {
        this.name = name;
        // Не верь никому! Делаем копию переданной даты.
        this.birthDate = new Date(birthDate.getTime());
        // И список копируем, мало ли что там заслали.
        this.hobbies = new ArrayList<>(hobbies);
    }

    public String getName() { return name; }

    public Date getBirthDate() {
        // Наружу тоже отдаём копию, а не оригинал. Пусть себе вертят.
        return new Date(birthDate.getTime());
    }

    public List<String> getHobbies() {
        // Возвращаем обёртку, которую нельзя изменить. Попробуй добавить хобби — получишь ебаный Exception.
        return Collections.unmodifiableList(new ArrayList<>(hobbies));
    }
}

А если полей, блядь, овердохуища, и конструктор получается на три экрана — не городи эту поебень. Используй паттерн Builder. Он тебе всё красиво соберёт по кирпичику, а в конце выдаст готовый, намертво зафиксированный объект. Удобно, как хуй с винтом.