Если бы пришлось имплементировать иммутабельный класс на Java, как бы вы это сделали?android-219

Основные принципы иммутабельности

Иммутабельный класс — это класс, состояние которого нельзя изменить после создания. Основные характеристики:

  • Все поля объявлены как final
  • Нет сеттеров (методов изменения состояния)
  • Запрещено наследование
  • Защита от внешнего изменения mutable-полей

Полный шаблон реализации

public final class ImmutablePerson {
    // 1. Все поля final и private
    private final String name;
    private final int age;
    private final List<String> hobbies; // mutable-объект

    // 2. Параметры конструктора - все необходимые поля
    public ImmutablePerson(String name, int age, List<String> hobbies) {
        this.name = name;
        this.age = age;
        // 3. Глубокая копия для mutable-объектов
        this.hobbies = new ArrayList<>(hobbies);
    }

    // 4. Только геттеры
    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    // 5. Для mutable-полей возвращаем копию или неизменяемую версию
    public List<String> getHobbies() {
        return Collections.unmodifiableList(hobbies);
        // Альтернатива: return new ArrayList<>(hobbies);
    }

    // 6. Методы "изменения" возвращают новый объект
    public ImmutablePerson withAge(int newAge) {
        return new ImmutablePerson(this.name, newAge, this.hobbies);
    }
}

Ключевые моменты реализации

1. Защита от наследования

  • Класс объявлен как final
  • Нет методов, которые можно переопределить

2. Безопасная публикация

  • Все поля инициализируются в конструкторе
  • Нет возможности частичной инициализации

3. Работа с mutable-объектами

Для полей, содержащих изменяемые объекты (коллекции, массивы):

  • В конструкторе создаем копию
  • В геттерах возвращаем копию или unmodifiable-обертку

4. Методы "модификации"

Реализуются через создание нового объекта:

public ImmutablePerson addHobby(String hobby) {
    List<String> newHobbies = new ArrayList<>(this.hobbies);
    newHobbies.add(hobby);
    return new ImmutablePerson(this.name, this.age, newHobbies);
}

Преимущества иммутабельных классов

  1. Потокобезопасность:

    • Не требуется синхронизация
    • Безопасное использование в многопоточной среде
  2. Предсказуемость:

    • Состояние объекта никогда не меняется неожиданно
  3. Кэширование и повторное использование:

    • Можно безопасно кэшировать и возвращать один экземпляр
  4. Упрощение тестирования:

    • Нет побочных эффектов

Пример из Android SDK

public final class Location {
    private final double latitude;
    private final double longitude;

    public Location(double latitude, double longitude) {
        this.latitude = latitude;
        this.longitude = longitude;
    }

    // Только геттеры
    public double getLatitude() {
        return latitude;
    }
}

Специальные случаи

1. Сериализация

Для правильной десериализации может потребоваться:

  • Объявить конструктор без параметров
  • Пометить поля как @Serial

2. Большие объекты

При частом "изменении" через копирование:

  • Рассмотреть паттерн Builder
  • Использовать структурное разделение

Резюмируем:

Реализация иммутабельного класса в Java требует строгого соблюдения нескольких правил — final-поля, защита mutable-состояния, запрет наследования и правильная работа с методами доступа. Это обеспечивает безопасность, предсказуемость и простоту поддержки кода.