Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как создать Immutable (неизменяемый) класс в Java
Immutable класс — это класс, экземпляры которого не могут быть изменены после создания. Это критически важно для многопоточного программирования, так как immutable объекты потокобезопасны по определению и не требуют синхронизации.
Правила создания Immutable класса
Для создания истинно неизменяемого класса нужно следовать этим правилам:
- Поля должны быть private и final
- Нет setter методов
- Класс должен быть final (чтобы нельзя было создать mutable подкласс)
- Конструктор должен быть единственным способом инициализации
- Если поля содержат mutable объекты, нужно делать defensive copy
- Методы должны возвращать новые объекты, а не модифицировать существующие
Базовый пример: Immutable User
// ✅ ПРАВИЛЬНО: Immutable класс
public final class User {
private final long id;
private final String name;
private final String email;
private final int age;
// Единственный конструктор
public User(long id, String name, String email, int age) {
this.id = id;
this.name = name;
this.email = email;
this.age = age;
}
// Только getter методы, БЕЗ setters
public long getId() {
return id;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public int getAge() {
return age;
}
// ВАЖНО: реализовать equals, hashCode, toString
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
return id == user.id &&
age == user.age &&
Objects.equals(name, user.name) &&
Objects.equals(email, user.email);
}
@Override
public int hashCode() {
return Objects.hash(id, name, email, age);
}
@Override
public String toString() {
return "User{" + "id=" + id + ", name='" + name + "'" +
", email='" + email + "'" + ", age=" + age + "}";
}
}
// Использование
User user = new User(1L, "John Doe", "john@example.com", 30);
System.out.println(user); // User{id=1, name='John Doe', email='john@example.com', age=30}
// Невозможно изменить поля (нет setters)
// user.setName("Jane"); // Compile error!
Сложный случай: Immutable с mutable полями
Если класс содержит ссылки на mutable объекты (List, Map, Date), нужно делать defensive copy:
// ❌ НЕПРАВИЛЬНО: содержит ссылку на mutable List
public final class BadTeam {
private final String name;
private final List<String> members; // Ссылка на mutable объект!
public BadTeam(String name, List<String> members) {
this.name = name;
this.members = members; // ПРОБЛЕМА: внешний код может изменить список
}
public List<String> getMembers() {
return members; // ПРОБЛЕМА: возвращаем ссылку, которую можно изменить
}
}
// Использование
List<String> teamMembers = new ArrayList<>(Arrays.asList("Alice", "Bob"));
BadTeam team = new BadTeam("Developers", teamMembers);
teamMembers.add("Charlie"); // Изменяем через внешнюю ссылку!
// ✅ ПРАВИЛЬНО: делаем defensive copy
public final class GoodTeam {
private final String name;
private final List<String> members; // Внутренняя ссылка
// Defensive copy в конструкторе
public GoodTeam(String name, List<String> members) {
this.name = Objects.requireNonNull(name, "Name cannot be null");
// Создаём неизменяемую копию
this.members = Collections.unmodifiableList(new ArrayList<>(members));
}
// Defensive copy при возврате
public List<String> getMembers() {
// Возвращаем неизменяемый список
return Collections.unmodifiableList(members);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof GoodTeam)) return false;
GoodTeam goodTeam = (GoodTeam) o;
return Objects.equals(name, goodTeam.name) &&
Objects.equals(members, goodTeam.members);
}
@Override
public int hashCode() {
return Objects.hash(name, members);
}
}
// Использование
List<String> teamMembers = new ArrayList<>(Arrays.asList("Alice", "Bob"));
GoodTeam team = new GoodTeam("Developers", teamMembers);
teamMembers.add("Charlie"); // Не влияет на team!
System.out.println(team.getMembers()); // [Alice, Bob]
Пример с Date (mutable объект)
// ❌ НЕПРАВИЛЬНО
public final class BadEvent {
private final String name;
private final Date date; // Date is mutable!
public BadEvent(String name, Date date) {
this.name = name;
this.date = date; // Проблема
}
public Date getDate() {
return date; // Проблема: можно изменить
}
}
// ✅ ПРАВИЛЬНО
public final class GoodEvent {
private final String name;
private final LocalDateTime dateTime; // LocalDateTime immutable
public GoodEvent(String name, LocalDateTime dateTime) {
this.name = Objects.requireNonNull(name);
this.dateTime = Objects.requireNonNull(dateTime);
}
// Или с Date (делаем defensive copy)
public GoodEvent(String name, Date date) {
this.name = Objects.requireNonNull(name);
this.dateTime = new java.time.Instant(date.getTime())
.atZone(java.time.ZoneId.systemDefault())
.toLocalDateTime();
}
public LocalDateTime getDateTime() {
return dateTime; // Безопасно возвращать (immutable)
}
}
Builder паттерн для создания Immutable объектов
Для сложных immutable объектов удобно использовать Builder:
public final class Person {
private final String firstName;
private final String lastName;
private final int age;
private final String email;
private final String phone;
private final Address address;
private Person(Builder builder) {
this.firstName = builder.firstName;
this.lastName = builder.lastName;
this.age = builder.age;
this.email = builder.email;
this.phone = builder.phone;
this.address = builder.address;
}
// Builder class
public static class Builder {
private String firstName;
private String lastName;
private int age;
private String email;
private String phone;
private Address address;
public Builder firstName(String firstName) {
this.firstName = firstName;
return this;
}
public Builder lastName(String lastName) {
this.lastName = lastName;
return this;
}
public Builder age(int age) {
this.age = age;
return this;
}
public Builder email(String email) {
this.email = email;
return this;
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
public Builder address(Address address) {
this.address = address;
return this;
}
// Validation in build()
public Person build() {
if (firstName == null || firstName.isBlank()) {
throw new IllegalArgumentException("First name is required");
}
if (lastName == null || lastName.isBlank()) {
throw new IllegalArgumentException("Last name is required");
}
return new Person(this);
}
}
// Getters only
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public int getAge() { return age; }
public String getEmail() { return email; }
public String getPhone() { return phone; }
public Address getAddress() { return address; }
@Override
public String toString() {
return "Person{" + "firstName='" + firstName + "'" +
", lastName='" + lastName + "'" + ", age=" + age + "}";
}
}
// Использование Builder
Person person = new Person.Builder()
.firstName("John")
.lastName("Doe")
.age(30)
.email("john@example.com")
.phone("+1234567890")
.build();
Records как готовое решение (Java 16+)
Java Records — это встроенный способ создания immutable классов:
// ✅ ИДЕАЛЬНО: Records
public record User(long id, String name, String email, int age) {}
// Records автоматически:
// ✓ final поля
// ✓ final класс
// ✓ implements equals(), hashCode(), toString()
// ✓ Конструктор и getters
// Использование
User user = new User(1L, "John Doe", "john@example.com", 30);
System.out.println(user); // User[id=1, name=John Doe, email=john@example.com, age=30]
// С validation
public record Person(String name, int age) {
public Person {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
}
}
Сравнение подходов
| Подход | Boilerplate | Простота | Современность |
|---|---|---|---|
| Обычный класс | Много | Простая | 2000s |
| Builder паттерн | Среднее | Сложная | 2006+ |
| Lombok @Value | Минимум | Простая | 2010s |
| Records | Минимум | Очень простая | 2021+ |
Преимущества Immutable классов
✅ Thread-safe по определению (не нужны locks) ✅ Можно кэшировать (всегда одинаковые) ✅ Безопасны в Map/Set (hashCode не меняется) ✅ Легче тестировать (predictable behavior) ✅ Защита от побочных эффектов (defensive copy) ✅ Functional programming friendly
Итоги
Для создания immutable класса:
- Сделай класс final
- Поля private final
- Нет setters
- Делай defensive copies для mutable объектов
- Используй Records (Java 16+) если возможно
- Реализуй equals(), hashCode(), toString()