Неизменяемый объект не может быть изменен после его создания — каждое поле устанавливается один раз, и нет методов, которые мутируют состояние. Чтобы его "изменить", вы создаете новый объект.
Построение неизменяемого типа
public final class Money { // final: can't be subclassed to add mutation
private final int cents; // final fields, set once
private final String currency;
public Money(int cents, String currency) {
this.cents = cents;
this.currency = currency;
}
public Money plus(Money other) { // returns a NEW object, no mutation
return new Money(this.cents + other.cents, currency);
}
public int cents() { return cents; }
}
plus не изменяет this; она возвращает новый Money.
Почему неизменяемость помогает
✓ Thread-safe by default → no shared mutable state, no locks needed
✓ Safe to share/cache → no defensive copies
✓ Valid forever → if valid at construction, stays valid
✓ Usable as map keys → hashCode never changes
Компромиссы
⚠️ Creates new objects on every change → more allocation/GC pressure
⚠️ Awkward for large objects updated frequently (mitigated by builders / persistent
data structures)
Осторожно с поверхностной неизменяемостью: поле final, указывающее на изменяемый список, все равно изменяемо через эту ссылку. Выполняйте защитное копирование или используйте неизменяемые коллекции.
Почему это важно
Неизменяемость устраняет целый класс ошибок: ничто не может изменить объект из-под вас, поэтому рассуждение и конкурентность становятся драматически проще.
Вот почему типы значений (strings, LocalDate, records) неизменяемы в современных языках — они общие, кэшируются и безопасно используются в качестве ключей без защитного копирования.
