← Назад к вопросам

Как описать класс, чтобы он был Immutable в Java?

2.0 Middle🔥 122 комментариев
#Java

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое Immutable Class и зачем он нужен?

Immutable Class (Неизменяемый класс) — это класс, состояние которого нельзя изменить после создания объекта. Все поля объявляются как final, отсутствуют сеттеры, и класс запрещает наследников. Такой подход обеспечивает потокобезопасность без синхронизации, упрощает кэширование, использование в качестве ключей HashMap и является фундаментом функционального программирования.

Ключевые шаги создания Immutable класса в Java

1. Объявите класс как final

Это предотвращает наследование и переопределение методов, которые могли бы изменить состояние.

public final class ImmutablePerson {
    // Поля объявлены как private final
}

2. Объявите все поля как private final

Модификатор private скрывает поля, а final гарантирует однократную инициализацию только в конструкторе.

private final String name;
private final int age;
private final List<String> hobbies; // Важно: для mutable полей нужна особая обработка

3. Не предоставляйте сеттеры (setter methods)

Любые методы, меняющие состояние объекта, запрещены.

4. Инициализируйте все поля в конструкторе

Все значения должны быть установлены при создании объекта и больше не меняться.

public ImmutablePerson(String name, int age, List<String> hobbies) {
    this.name = name;
    this.age = age;
    this.hobbies = new ArrayList<>(hobbies); // Защитное копирование!
}

5. Осуществляйте защитное копирование (defensive copying) для mutable полей

Если класс содержит ссылки на изменяемые объекты (коллекции, массивы, другие классы), необходимо:

  • В конструкторе создавать копии входящих mutable объектов
  • В геттерах возвращать копии mutable полей
// Конструктор с защитным копированием
public ImmutablePerson(String name, int age, List<String> hobbies) {
    this.name = name;
    this.age = age;
    // Создаем новую коллекцию на основе переданной
    this.hobbies = hobbies != null ? new ArrayList<>(hobbies) : Collections.emptyList();
}

// Геттер с защитным копированием
public List<String> getHobbies() {
    // Возвращаем копию, а не оригинальную коллекцию
    return new ArrayList<>(hobbies);
}

6. Для массивов используйте Arrays.copyOf()

private final String[] tags;

public ImmutablePerson(String[] tags) {
    this.tags = tags != null ? Arrays.copyOf(tags, tags.length) : new String[0];
}

public String[] getTags() {
    return Arrays.copyOf(tags, tags.length);
}

7. Рассмотрите кэширование часто используемых значений

Для оптимизации можно кэшировать результаты вычислений, например хэш-код.

private volatile int hashCode; // Кэшированный хэш-код

@Override
public int hashCode() {
    if (hashCode == 0) {
        // Вычисление хэш-кода
        hashCode = Objects.hash(name, age, hobbies);
    }
    return hashCode;
}

Полный пример Immutable класса

import java.util.*;

public final class ImmutablePerson {
    private final String name;
    private final int age;
    private final List<String> hobbies;
    private final Date birthDate; // mutable поле!

    public ImmutablePerson(String name, int age, List<String> hobbies, Date birthDate) {
        this.name = name;
        this.age = age;
        // Защитное копирование для коллекции
        this.hobbies = hobbies != null ? 
            Collections.unmodifiableList(new ArrayList<>(hobbies)) : 
            Collections.emptyList();
        // Защитное копирование для Date
        this.birthDate = birthDate != null ? new Date(birthDate.getTime()) : null;
    }

    // Геттеры
    public String getName() { return name; }
    public int getAge() { return age; }
    
    public List<String> getHobbies() {
        // Возвращаем unmodifiableList
        return Collections.unmodifiableList(new ArrayList<>(hobbies));
    }
    
    public Date getBirthDate() {
        // Возвращаем копию mutable объекта
        return birthDate != null ? new Date(birthDate.getTime()) : null;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ImmutablePerson that = (ImmutablePerson) o;
        return age == that.age && 
               Objects.equals(name, that.name) && 
               Objects.equals(hobbies, that.hobbies) && 
               Objects.equals(birthDate, that.birthDate);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, hobbies, birthDate);
    }
}

Преимущества Immutable классов

  • Потокобезопасность: Не требуют синхронизации, можно свободно использовать в многопоточных приложениях
  • Простота тестирования: Состояние объекта неизменно, поведение предсказуемо
  • Безопасность использования как ключей: В HashMap и других коллекциях
  • Кэширование: Можно безопасно кэшировать и переиспользовать объекты
  • Отказоустойчивость: Исключения не оставят объект в некорректном состоянии

Рекомендации для современных Java проектов

  1. Для создания простых immutable классов используйте records (Java 16+):
public record PersonRecord(String name, int age, List<String> hobbies) {
    // Конструктор по умолчанию уже делает защитное копирование
    public PersonRecord {
        hobbies = List.copyOf(hobbies); // Создает unmodifiable копию
    }
}
  1. Используйте Collections.unmodifiableXXX() для возврата коллекций
  2. Для сложных объектов рассмотрите паттерн Builder
  3. Используйте immutable коллекции из библиотек Guava или Vavr

Immutable классы — это мощный инструмент для создания надежного, безопасного и легко поддерживаемого кода, особенно в современных многопоточных приложениях и микросервисных архитектурах.

Как описать класс, чтобы он был Immutable в Java? | PrepBro