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

Как создать иммутабельный класс?

2.2 Middle🔥 121 комментариев
#Основы Java#ООП

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как создать иммутабельный класс

Иммутабельный класс (Immutable Class) — это класс, состояние которого не может быть изменено после создания. Это мощный паттерн для безопасности потоков и функционального программирования в Java.

Основные правила создания иммутабельного класса

  1. Делай все поля final
  2. Делай класс final (чтобы нельзя было наследовать)
  3. Не предоставляй setter методов
  4. Делай поля private
  5. Для mutable объектов в полях — возвращай копии
  6. Инициализируй поля только в конструкторе

Пример базового иммутабельного класса

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;
    }
    
    // Без setters!
    // public void setName(String name) { ... }  // ❌ Неправильно
}

// Использование
Person person = new Person("John", 25, "john@example.com");
System.out.println(person.getName());  // John

// Попытка изменить — невозможно
// person.name = "Jane";  // ❌ Compile error (private final)

Проблема: Mutable поля внутри

// ❌ Неправильно — можно изменить содержимое List
public final class Team {
    private final List<String> members;  // final, но List мутабельный!
    
    public Team(List<String> members) {
        this.members = members;
    }
    
    public List<String> getMembers() {
        return members;
    }
}

// Проблема
List<String> list = new ArrayList<>();
list.add("John");
Team team = new Team(list);

list.add("Jane");  // Изменяем исходный список
System.out.println(team.getMembers());  // [John, Jane] — изменился!

// Ещё проблема
team.getMembers().add("Bob");  // Можно изменить через getter
System.out.println(team.getMembers());  // [John, Jane, Bob] — изменился!

Решение: Копирование mutable объектов

public final class Team {
    private final List<String> members;
    
    // Конструктор — копируем входящий список
    public Team(List<String> members) {
        this.members = new ArrayList<>(members);  // Копия!
    }
    
    // Getter — возвращаем неизменяемый список или копию
    public List<String> getMembers() {
        return Collections.unmodifiableList(members);  // Неизменяемый вид
    }
    
    // Или возвращаем новую копию
    public List<String> getMembersCopy() {
        return new ArrayList<>(members);
    }
}

// Использование
List<String> list = new ArrayList<>();
list.add("John");
Team team = new Team(list);

list.add("Jane");  // Изменяем исходный список
System.out.println(team.getMembers());  // [John] — не изменился!

// Попытка изменить через getter
try {
    team.getMembers().add("Bob");  // Throws UnsupportedOperationException
} catch (UnsupportedOperationException e) {
    System.out.println("Cannot modify");
}

Полный пример иммутабельного класса

import java.time.LocalDate;
import java.util.Collections;
import java.util.List;

public final class User {
    // Все поля final и private
    private final long id;
    private final String username;
    private final String email;
    private final LocalDate birthDate;
    private final List<String> roles;
    
    // Конструктор с валидацией
    public User(
        long id,
        String username,
        String email,
        LocalDate birthDate,
        List<String> roles
    ) {
        // Валидация
        if (username == null || username.isEmpty()) {
            throw new IllegalArgumentException("Username cannot be empty");
        }
        if (email == null || !email.contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }
        
        this.id = id;
        this.username = username;
        this.email = email;
        this.birthDate = birthDate;
        // Копируем список для защиты от внешних изменений
        this.roles = new ArrayList<>(roles != null ? roles : List.of());
    }
    
    // Getters
    public long getId() {
        return id;
    }
    
    public String getUsername() {
        return username;
    }
    
    public String getEmail() {
        return email;
    }
    
    public LocalDate getBirthDate() {
        return birthDate;
    }
    
    public List<String> getRoles() {
        // Возвращаем неизменяемый список
        return Collections.unmodifiableList(roles);
    }
    
    // НЕТ setters!
    
    // Если нужно изменить — создаём новый объект (Builder pattern)
    public User withUsername(String newUsername) {
        return new User(this.id, newUsername, this.email, this.birthDate, this.roles);
    }
    
    public User withRoles(List<String> newRoles) {
        return new User(this.id, this.username, this.email, this.birthDate, newRoles);
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return id == user.id &&
               username.equals(user.username) &&
               email.equals(user.email);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(id, username, email);
    }
    
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", email='" + email + '\'' +
                '}';
    }
}

// Использование
User user = new User(
    1L,
    "john_doe",
    "john@example.com",
    LocalDate.of(1990, 5, 15),
    List.of("USER", "ADMIN")
);

// Неизменяемо
System.out.println(user.getUsername());  // john_doe

// Попытка изменить — невозможно
// user.setUsername("jane");  // ❌ Compile error

// Если нужно изменить — создаём новый объект
User updatedUser = user.withUsername("john_smith");
System.out.println(user.getUsername());        // john_doe (исходный не изменился)
System.out.println(updatedUser.getUsername()); // john_smith (новый объект)

Builder Pattern для иммутабельных классов

public final class User {
    private final String username;
    private final String email;
    private final LocalDate birthDate;
    private final List<String> roles;
    
    private User(Builder builder) {
        this.username = builder.username;
        this.email = builder.email;
        this.birthDate = builder.birthDate;
        this.roles = new ArrayList<>(builder.roles);
    }
    
    public static class Builder {
        private final String username;  // Обязательное
        private final String email;     // Обязательное
        private LocalDate birthDate;    // Опциональное
        private List<String> roles = new ArrayList<>();
        
        public Builder(String username, String email) {
            this.username = username;
            this.email = email;
        }
        
        public Builder birthDate(LocalDate birthDate) {
            this.birthDate = birthDate;
            return this;
        }
        
        public Builder roles(List<String> roles) {
            this.roles = roles;
            return this;
        }
        
        public User build() {
            return new User(this);
        }
    }
    
    // Getters
    public String getUsername() { return username; }
    public String getEmail() { return email; }
    public LocalDate getBirthDate() { return birthDate; }
    public List<String> getRoles() {
        return Collections.unmodifiableList(roles);
    }
}

// Использование Builder
User user = new User.Builder("john", "john@example.com")
    .birthDate(LocalDate.of(1990, 5, 15))
    .roles(List.of("USER"))
    .build();

Record (Java 16+) — более простой способ

// Record автоматически создаёт иммутабельный класс
public record User(
    long id,
    String username,
    String email,
    LocalDate birthDate,
    List<String> roles
) {
    // Компактный конструктор для валидации
    public User {
        if (username == null || username.isEmpty()) {
            throw new IllegalArgumentException("Username cannot be empty");
        }
        roles = Collections.unmodifiableList(roles == null ? List.of() : roles);
    }
}

// Использование — как обычно
User user = new User(1L, "john", "john@example.com", LocalDate.now(), List.of("USER"));
System.out.println(user.username());  // john

// Record автоматически создаёт equals, hashCode, toString
System.out.println(user);  // User[id=1, username=john, ...]

Преимущества иммутабельных классов

  1. Thread Safety — можно безопасно использовать в многопоточной среде
public final class ImmutableCounter {
    private final int count;
    
    public ImmutableCounter(int count) {
        this.count = count;
    }
    
    public int getCount() {
        return count;  // Безопасно без синхронизации
    }
    
    public ImmutableCounter increment() {
        return new ImmutableCounter(count + 1);  // Новый объект
    }
}
  1. Безопасность для кэширования и коллекций
Map<ImmutableUser, String> cache = new HashMap<>();  // Можно безопасно использовать как ключ
  1. Проще отслеживать изменения
ImmutableUser originalUser = new ImmutableUser("john");
ImmutableUser modifiedUser = originalUser.withName("jane");  // Ясно видно изменение

Лучшие практики

  1. Используй final для класса и полей
  2. Нет setters
  3. Копируй mutable объекты в конструкторе
  4. Возвращай неизменяемые представления для mutable полей
  5. Реализуй equals(), hashCode(), toString()
  6. Используй Builder для сложных конструкторов
  7. Рассмотри использование Record (Java 16+)

Иммутабельные классы — это основа функционального программирования и безопасности в Java.

Как создать иммутабельный класс? | PrepBro