Чем полезны неизменяемые объекты?
Неизменяемые (immutable) объекты — это объекты, состояние которых не может быть изменено после создания. В контексте многопоточности их главное преимущество: неизменяемый объект можно безопасно использовать из любого количества потоков без какой-либо синхронизации.
Почему это работает: все проблемы многопоточности (race condition, visibility, happens-before) связаны с изменяемым общим состоянием. Если состояние не может измениться, нет гонок, нет нужды в блокировках, нет риска увидеть «промежуточное» значение.
Правила создания неизменяемого класса:
- Объявите класс как
final(или все конструкторы —private), чтобы предотвратить наследование с переопределением поведения. - Все поля —
privateиfinal. - Не предоставляйте методы, изменяющие состояние (никаких
setX()). - Инициализируйте все поля в конструкторе.
- Для мутабельных полей (коллекции, массивы, даты) делайте защитные копии — и при получении в конструкторе, и при отдаче через геттеры.
- Не допускайте утечки ссылки
thisиз конструктора.
Код: пример корректного неизменяемого класса
public final class ImmutablePerson {
private final String name;
private final List<String> hobbies;
public ImmutablePerson(String name, List<String> hobbies) {
this.name = name;
this.hobbies = List.copyOf(hobbies); // Защитная копия (Java 10+)
}
public String getName() {
return name;
}
public List<String> getHobbies() {
return hobbies; // List.copyOf возвращает unmodifiable list
}
}
Примеры неизменяемых классов в JDK: String, Integer и другие обёртки примитивов, BigInteger, BigDecimal, LocalDate, LocalTime, LocalDateTime, Optional.
Использование в многопоточном контексте:
Пример
// Неизменяемый объект можно безопасно передавать между потоками
// без synchronized, volatile или AtomicReference
final ImmutablePerson person = new ImmutablePerson("Иван", List.of("Java", "Шахматы"));
ExecutorService pool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
pool.submit(() -> {
// Безопасно: person не может измениться
System.out.println(person.getName() + ": " + person.getHobbies());
});
}
Java Records (Java 16+) — удобный способ создания неизменяемых объектов:
Пример
public record Person(String name, List<String> hobbies) {
public Person {
hobbies = List.copyOf(hobbies); // Защитная копия в компактном конструкторе
}
}
Аналогия: неизменяемый объект — это напечатанная книга. Любое количество людей может читать её одновременно, не мешая друг другу. Изменяемый объект — это Google-документ: при одновременном редактировании нужна координация, иначе будет хаос.
На собеседовании часто спрашивают: «Почему
Stringсделан неизменяемым?» Одна из причин — потокобезопасность.Stringактивно используется как ключ вHashMap, параметр класса, имя файла и т.д. Если быStringбыл мутабельным, каждое его использование в многопоточной среде потребовало бы синхронизации.