Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Имутабельные (неизменяемые) объекты
Имутабельный объект — это объект, состояние которого не может быть изменено после его создания. Все значения полей устанавливаются в момент инициализации и остаются неизменными на протяжении всей жизни объекта. Это фундаментальный принцип функционального программирования, который обеспечивает потокобезопасность, предсказуемость и облегчает отладку.
Характеристики имутабельных объектов
1. Все поля финальные и приватные
public final class Person {
private final String name;
private final int age;
private final String email;
// Конструктор, устанавливающий все поля
public Person(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
// Только getters, никаких setters
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getEmail() {
return email;
}
}
2. Класс объявлен как final
Предотвращает создание подклассов, которые могли бы нарушить имутабельность:
// Класс final — нельзя наследоваться
public final class Money {
private final BigDecimal amount;
private final Currency currency;
public Money(BigDecimal amount, Currency currency) {
this.amount = amount;
this.currency = currency;
}
}
// ❌ ОШИБКА: нельзя расширить
// public class ExtendedMoney extends Money { }
3. Нет сеттеров
Имутабельные объекты предоставляют только методы для чтения данных:
public final class User {
private final String username;
private final String email;
private final LocalDateTime createdAt;
public User(String username, String email) {
this.username = username;
this.email = email;
this.createdAt = LocalDateTime.now();
}
// Только getters
public String getUsername() {
return username;
}
public String getEmail() {
return email;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
// НЕТ setters!
}
Встроенные имутабельные классы в Java
String
Один из самых известных имутабельных классов:
String original = "Hello";
String modified = original.concat(" World");
// original не изменён
System.out.println(original); // Hello
System.out.println(modified); // Hello World
// Строки с одинаковым значением указывают на один объект в памяти
String s1 = "test";
String s2 = "test";
System.out.println(s1 == s2); // true (один объект)
Числовые классы
// Integer, Long, Double, BigDecimal — имутабельные
Integer i = 42;
BigDecimal bd = new BigDecimal("100.50");
// Методы возвращают новые объекты
BigDecimal result = bd.add(new BigDecimal("10.00"));
System.out.println(bd); // 100.50 (не изменена)
System.out.println(result); // 110.50
Collections.unmodifiableXxx()
List<String> original = new ArrayList<>();
original.add("item1");
original.add("item2");
// Создаём неизменяемую копию
List<String> immutable = Collections.unmodifiableList(original);
// ❌ ОШИБКА: UnsupportedOperationException
// immutable.add("item3");
System.out.println(immutable); // [item1, item2]
Создание имутабельных объектов
1. Простой подход
public final class Address {
private final String street;
private final String city;
private final String country;
private final String zipCode;
public Address(String street, String city, String country, String zipCode) {
this.street = street;
this.city = city;
this.country = country;
this.zipCode = zipCode;
}
public String getStreet() { return street; }
public String getCity() { return city; }
public String getCountry() { return country; }
public String getZipCode() { return zipCode; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Address address = (Address) o;
return Objects.equals(street, address.street) &&
Objects.equals(city, address.city) &&
Objects.equals(country, address.country) &&
Objects.equals(zipCode, address.zipCode);
}
@Override
public int hashCode() {
return Objects.hash(street, city, country, zipCode);
}
}
2. Builder паттерн
public final class Book {
private final String title;
private final String author;
private final int pages;
private final String isbn;
private Book(Builder builder) {
this.title = builder.title;
this.author = builder.author;
this.pages = builder.pages;
this.isbn = builder.isbn;
}
public static class Builder {
private String title;
private String author;
private int pages;
private String isbn;
public Builder title(String title) {
this.title = title;
return this;
}
public Builder author(String author) {
this.author = author;
return this;
}
public Builder pages(int pages) {
this.pages = pages;
return this;
}
public Builder isbn(String isbn) {
this.isbn = isbn;
return this;
}
public Book build() {
return new Book(this);
}
}
public String getTitle() { return title; }
public String getAuthor() { return author; }
public int getPages() { return pages; }
public String getIsbn() { return isbn; }
}
// Использование
Book book = new Book.Builder()
.title("Clean Code")
.author("Robert Martin")
.pages(464)
.isbn("0132350882")
.build();
3. Record (Java 14+)
Записи автоматически создают имутабельные классы:
// Java 14+: record автоматически создаёт final класс
public record Product(
String name,
BigDecimal price,
int quantity
) {}
// Использование
Product product = new Product("Laptop",
new BigDecimal("999.99"), 5);
System.out.println(product.name()); // Laptop
System.out.println(product.price()); // 999.99
System.out.println(product.quantity()); // 5
// record автоматически генерирует equals(), hashCode(), toString()
Преимущества имутабельности
1. Потокобезопасность
// Имутабельный объект безопасен для использования в многопоточности
public final class ThreadSafeCounter {
private final int value;
public ThreadSafeCounter(int value) {
this.value = value;
}
// Синхронизация не требуется — состояние не меняется
public ThreadSafeCounter increment() {
return new ThreadSafeCounter(value + 1);
}
public int getValue() {
return value;
}
}
// Безопасно использовать из разных потоков
ThreadSafeCounter counter = new ThreadSafeCounter(0);
// Несколько потоков могут читать counter одновременно
2. Кэширование и hashCode
public final class Person {
private final String name;
private final int age;
private final int hash; // Кэшированный hashCode
public Person(String name, int age) {
this.name = name;
this.age = age;
this.hash = Objects.hash(name, age);
}
@Override
public int hashCode() {
return hash; // Вычисляется один раз при создании
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
}
3. Безопасность при передаче
// Имутабельные объекты можно безопасно передавать
public class Account {
private final Person owner; // Имутабельный
public Account(Person owner) {
this.owner = owner; // Безопасно — owner не может измениться
}
public Person getOwner() {
return owner; // Безопасно возвращать напрямую
}
}
Недостатки
Производительность памяти
// Каждое изменение требует создания нового объекта
Counter counter = new Counter(0);
for (int i = 0; i < 1_000_000; i++) {
counter = counter.increment(); // Создаётся 1 млн объектов
}
Рекомендации
- Используй имутабельные объекты для данных, которые не меняются
- Используй Builder для объектов с множеством полей
- Используй record для простых имутабельных объектов (Java 14+)
- Преимущества в потокобезопасности и предсказуемости часто перевешивают затраты памяти
Имутабельные объекты — ключевой принцип современного Java, обеспечивающий безопасность и надёжность кода.