← Назад к вопросам

Что такое имутабельные объекты?

1.8 Middle🔥 161 комментариев
#Основы Java

Комментарии (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, обеспечивающий безопасность и надёжность кода.

Что такое имутабельные объекты? | PrepBro