Карта / ООП / Immutable класс

Immutable класс

Immutable (неизменяемый) класс — объект, состояние которого нельзя поменять после создания. Создал — и оно навсегда такое. String — самый известный пример из стандартной библиотеки.

Чтобы сделать свой класс неизменяемым, нужно выполнить четыре правила:

final class Point {                       // 1. класс final — нельзя унаследоваться и сломать гарантию
    private final int x;                  // 2. поля final
    private final int y;

    Point(int x, int y) {                 // 3. значения только через конструктор
        this.x = x;
        this.y = y;
    }

    int getX() { return x; }              // 4. только геттеры, без сеттеров
    int getY() { return y; }
}

Если нужно “изменить” — создаёшь новый объект:

Point moved(Point p, int dx, int dy) {
    return new Point(p.getX() + dx, p.getY() + dy);   // новый объект, старый не тронут
}
Копнуть глубже

Главная ловушка — изменяемые поля внутри. Если поле само — изменяемый объект (например, List или Date), final на ссылке не спасает: содержимое можно поменять изнутри.

final class Team {
    private final List<String> members;
    Team(List<String> members) {
        this.members = members;   // ❌ опасно — хранится та же ссылка
    }
    List<String> getMembers() { return members; }  // ❌ отдаёт прямой доступ
}

Team t = new Team(new ArrayList<>(List.of("Артур")));
t.getMembers().add("Хакер");   // 💥 изменили "неизменяемый" объект

Правильно — защитное копирование (defensive copy) на входе и выходе:

Team(List<String> members) {
    this.members = List.copyOf(members);   // копия при создании
}
List<String> getMembers() {
    return List.copyOf(members);           // копия при отдаче
}
Под капотом

Зачем вообще нужна неизменяемость:

  • Потокобезопасность бесплатно. Несколько потоков могут читать один immutable-объект одновременно без блокировок — поменять его всё равно нельзя, гонки данных невозможны в принципе.
  • Надёжность как ключ в HashMap. Если объект — ключ коллекции, его hashCode не должен меняться после вставки, иначе запись “потеряется” (тот же принцип, что у String — см. тему «String»).
  • Предсказуемость. Передал объект в чужой метод — можешь быть уверен, что он не изменится у тебя за спиной.

Цена — каждое “изменение” создаёт новый объект, что может быть дороже по памяти, чем мутация на месте. Это сознательный компромисс: безопасность в обмен на лишние аллокации.

🎤 Закрыл тему, если можешь объяснить:
• четыре правила, чтобы сделать класс неизменяемым;
• что такое защитное копирование и зачем оно нужно (если дошёл до 2-го слоя).