Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как создать иммутабельный класс
Иммутабельный класс (Immutable Class) — это класс, состояние которого не может быть изменено после создания. Это мощный паттерн для безопасности потоков и функционального программирования в Java.
Основные правила создания иммутабельного класса
- Делай все поля
final - Делай класс
final(чтобы нельзя было наследовать) - Не предоставляй setter методов
- Делай поля
private - Для mutable объектов в полях — возвращай копии
- Инициализируй поля только в конструкторе
Пример базового иммутабельного класса
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, ...]
Преимущества иммутабельных классов
- 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); // Новый объект
}
}
- Безопасность для кэширования и коллекций
Map<ImmutableUser, String> cache = new HashMap<>(); // Можно безопасно использовать как ключ
- Проще отслеживать изменения
ImmutableUser originalUser = new ImmutableUser("john");
ImmutableUser modifiedUser = originalUser.withName("jane"); // Ясно видно изменение
Лучшие практики
- Используй
finalдля класса и полей - Нет setters
- Копируй mutable объекты в конструкторе
- Возвращай неизменяемые представления для mutable полей
- Реализуй
equals(),hashCode(),toString() - Используй Builder для сложных конструкторов
- Рассмотри использование Record (Java 16+)
Иммутабельные классы — это основа функционального программирования и безопасности в Java.