Что такое иммутабельность (immutability) в программировании?

Ответ

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

Зачем это нужно?

  • Потокобезопасность: Иммутабельные объекты можно безопасно использовать в многопоточных средах без блокировок.
  • Предсказуемость: Поведение кода становится более предсказуемым, так как объект не может неожиданно измениться в другой части программы.
  • Простота отладки и тестирования: Состояние объекта фиксировано.
  • Кеширование и повторное использование: Иммутабельные объекты с одинаковым состоянием могут быть одним и тем же объектом (как строки в пуле строк Java).

Пример в Java:

// Мутабельный класс
class MutablePoint {
    public int x, y;
    public MutablePoint(int x, int y) { this.x = x; this.y = y; }
}
MutablePoint p1 = new MutablePoint(1, 2);
MutablePoint p2 = p1; // p2 ссылается на тот же объект
p2.x = 10;
System.out.println(p1.x); // Выведет 10! p1 изменился неожиданно.

// Иммутабельный класс
final class ImmutablePoint {
    private final int x;
    private final int y;
    public ImmutablePoint(int x, int y) { this.x = x; this.y = y; }
    public int getX() { return x; }
    public int getY() { return y; }
    // Операция "изменения" возвращает новый объект
    public ImmutablePoint withX(int newX) {
        return new ImmutablePoint(newX, this.y);
    }
}
ImmutablePoint ip1 = new ImmutablePoint(1, 2);
ImmutablePoint ip2 = ip1.withX(10); // Создаётся новый объект
System.out.println(ip1.getX()); // Осталось 1
System.out.println(ip2.getX()); // Новое значение 10

В Java иммутабельность достигается через ключевое слово final для класса и полей, отсутствие сеттеров и возврат новых экземпляров в методах-модификаторах. Классы String, Integer, LocalDate являются иммутабельными.

Ответ 18+ 🔞

А, слушай, вот тема про иммутабельность. Это, бля, такая штука, когда объект, раз созданный, уже ни в какую не меняется. Как будто его в бетон залили, ёпта. Хочешь что-то поменять — изволь новый слепить, а старый пусть себе лежит, как музейный экспонат.

Ну и нахуя это надо, спросишь?

  • Чтобы потоки не перегрызлись. Представь, у тебя объект, и двадцать потоков на него смотрят. Если он иммутабельный — всем похуй, бери и читай, ничего не случится. А если мутабельный — это пиздец, волнение ебать, сейчас один поток ему хвост оторвёт, а другой будет искать, где он был. Блокировки, семафоры — геморрой полный.
  • Чтобы не охуеть от неожиданностей. Сделал объект, передал его в десять мест — и спи спокойно. Он не превратится в тыкву, пока ты не смотришь. Поведение кода прямолинейное, как рельс.
  • Чтобы тестировать и дебажить без истерик. Состояние зафиксировано — ищи баг где угодно, но не в том, что объект сам по себе эволюционировал.
  • Чтобы экономить память, если умный. Как строки в Java: если создал "Привет", а потом ещё раз "Привет" — система может тебе ту же самую ссылку подсунуть, потому что менять её всё равно нельзя. Умно, да?

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

// Это — мутабельный класс, распиздяй полный.
class MutablePoint {
    public int x, y; // Всё публично, делай что хочешь, хоть сломай.
    public MutablePoint(int x, int y) { this.x = x; this.y = y; }
}
MutablePoint p1 = new MutablePoint(1, 2);
MutablePoint p2 = p1; // p2 теперь смотрит на ТОТ ЖЕ объект, это не копия!
p2.x = 10; // Безобразие! Изменяем через p2...
System.out.println(p1.x); // Выведет 10! А p1-то ни сном ни духом, но его уже ебнули. Пизда рулю, предсказуемости ноль.

// А вот — иммутабельный класс, строгий и правильный.
final class ImmutablePoint {
    private final int x; // final — значит, после инита менять низя.
    private final int y;
    public ImmutablePoint(int x, int y) { this.x = x; this.y = y; }
    public int getX() { return x; } // Только геттеры, сеттеров — хуй!
    public int getY() { return y; }
    // Хочешь "поменять" X? Получи новый объект, хитрая жопа.
    public ImmutablePoint withX(int newX) {
        return new ImmutablePoint(newX, this.y);
    }
}
ImmutablePoint ip1 = new ImmutablePoint(1, 2);
ImmutablePoint ip2 = ip1.withX(10); // Создаётся НОВЫЙ объект, ip1 нетронут.
System.out.println(ip1.getX()); // Всё ещё 1, как и было. Успокойся.
System.out.println(ip2.getX()); // А вот тут уже 10, свеженькое.

В общем, в Java делаешь так: класс final, поля private final, сеттеров не даёшь, а если нужна модификация — возвращаешь новый инстанс. String, Integer, LocalDate — все они такие, иммутабельные красавчики. С ними хоть в огонь, хоть в воду — не изменятся, чувак.