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

Как можно преобразовать обычный класс в Immutable класс?

1.0 Junior🔥 61 комментариев
#Основы Java

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

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

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

Преобразование обычного класса в Immutable класс

Immutable (неизменяемый) класс — это объект, состояние которого не может быть изменено после создания. За 10+ лет опыта я часто использовал неизменяемые классы для повышения безопасности многопоточных приложений и создания более предсказуемого кода.

Правила для создания Immutable класса

Для преобразования обычного класса в неизменяемый необходимо следовать определённым правилам:

  1. Сделать класс final (предотвратить наследование)
  2. Все поля должны быть private и final
  3. Не предоставлять методов, изменяющих состояние
  4. Инициализировать поля только через конструктор
  5. Возвращать копии mutable объектов в getter'ах

Пример обычного класса (до преобразования)

// НЕПРАВИЛЬНО — mutable класс
public class Person {
    private String name;
    private int age;
    private List<String> hobbies;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
        this.hobbies = new ArrayList<>();
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public void setAge(int age) {
        this.age = age;
    }
    
    public void addHobby(String hobby) {
        this.hobbies.add(hobby);
    }
    
    public String getName() {
        return name;
    }
    
    public int getAge() {
        return age;
    }
    
    public List<String> getHobbies() {
        return hobbies;
    }
}

Этот класс опасен в многопоточной среде и нарушает принцип неизменяемости.

Преобразование в Immutable класс

Способ 1: Ручное преобразование

// ПРАВИЛЬНО — immutable класс
public final class Person {
    private final String name;
    private final int age;
    private final List<String> hobbies;
    
    // Конструктор инициализирует все поля
    public Person(String name, int age, List<String> hobbies) {
        this.name = name;
        this.age = age;
        // Создаём неизменяемую копию списка
        this.hobbies = Collections.unmodifiableList(
            new ArrayList<>(hobbies)
        );
    }
    
    // Getter без методов изменения
    public String getName() {
        return name;
    }
    
    public int getAge() {
        return age;
    }
    
    // Возвращаем копию list'а
    public List<String> getHobbies() {
        return new ArrayList<>(hobbies);
    }
    
    // Переопределяем equals и hashCode для сравнения
    @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) &&
               Objects.equals(hobbies, person.hobbies);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(name, age, hobbies);
    }
    
    @Override
    public String toString() {
        return "Person{" +
               "name='" + name + '\'' +
               ", age=" + age +
               ", hobbies=" + hobbies +
               '}';
    }
}

Способ 2: Использование Builder паттерна

public final class Person {
    private final String name;
    private final int age;
    private final List<String> hobbies;
    
    private Person(Builder builder) {
        this.name = builder.name;
        this.age = builder.age;
        this.hobbies = Collections.unmodifiableList(
            new ArrayList<>(builder.hobbies)
        );
    }
    
    // Builder для удобного создания
    public static class Builder {
        private String name;
        private int age;
        private List<String> hobbies = new ArrayList<>();
        
        public Builder name(String name) {
            this.name = name;
            return this;
        }
        
        public Builder age(int age) {
            this.age = age;
            return this;
        }
        
        public Builder addHobby(String hobby) {
            this.hobbies.add(hobby);
            return this;
        }
        
        public Person build() {
            if (name == null || age < 0) {
                throw new IllegalArgumentException("Invalid person data");
            }
            return new Person(this);
        }
    }
    
    // Getters
    public String getName() { return name; }
    public int getAge() { return age; }
    public List<String> getHobbies() { return new ArrayList<>(hobbies); }
}

// Использование
Person person = new Person.Builder()
    .name("John")
    .age(30)
    .addHobby("Reading")
    .addHobby("Gaming")
    .build();

Способ 3: Использование Record (Java 16+)

В Java 16+ Record автоматически создаёт immutable класс:

public record Person(
    String name,
    int age,
    List<String> hobbies
) {
    // Конструктор с валидацией (компактный конструктор)
    public Person {
        if (name == null || age < 0) {
            throw new IllegalArgumentException("Invalid person data");
        }
        // Копируем list для полной неизменяемости
        hobbies = Collections.unmodifiableList(
            new ArrayList<>(hobbies)
        );
    }
}

// Использование
Person person = new Person("John", 30, List.of("Reading", "Gaming"));
System.out.println(person.name()); // name() вместо getName()

Способ 4: Использование аннотаций Lombok

import lombok.Value;
import java.util.List;
import java.util.Collections;

@Value // Создаёт immutable класс с equals, hashCode, toString
public class Person {
    String name;
    int age;
    List<String> hobbies; // ВАЖНО: Lombok не копирует mutable объекты!
    
    // Кастомный конструктор для копирования списка
    public Person(String name, int age, List<String> hobbies) {
        this.name = name;
        this.age = age;
        this.hobbies = Collections.unmodifiableList(
            new ArrayList<>(hobbies)
        );
    }
}

Пример защиты от изменения mutable объектов

public final class ImmutablePerson {
    private final String name;
    private final int age;
    private final List<String> hobbies;
    
    public ImmutablePerson(String name, int age, List<String> hobbies) {
        this.name = name;
        this.age = age;
        // КРИТИЧНО: создаём копию и делаем её неизменяемой
        this.hobbies = Collections.unmodifiableList(
            new ArrayList<>(hobbies)
        );
    }
    
    public List<String> getHobbies() {
        // Возвращаем копию, а не прямую ссылку
        return new ArrayList<>(hobbies);
    }
}

// Попытка изменить
List<String> hobbyList = new ArrayList<>();
hobbyList.add("Reading");
ImmutablePerson person = new ImmutablePerson("John", 30, hobbyList);

// Попытка изменить через оригинальный список — не повлияет на person
hobbyList.add("Gaming");

// Попытка изменить через getter — выбросит исключение
List<String> hobbies = person.getHobbies();
hobbies.add("Coding"); // UnsupportedOperationException

Проверка на неизменяемость класса

public class ImmutabilityChecker {
    public static boolean isImmutable(Class<?> clazz) {
        // Класс должен быть final
        if (!Modifier.isFinal(clazz.getModifiers())) {
            return false;
        }
        
        // Все поля должны быть private final
        for (Field field : clazz.getDeclaredFields()) {
            int modifiers = field.getModifiers();
            if (!Modifier.isPrivate(modifiers) || !Modifier.isFinal(modifiers)) {
                return false;
            }
        }
        
        // Не должно быть методов с префиксом "set"
        for (Method method : clazz.getDeclaredMethods()) {
            if (method.getName().startsWith("set")) {
                return false;
            }
        }
        
        return true;
    }
}

Преимущества Immutable классов

  1. Thread-safety — безопасны в многопоточной среде без синхронизации
  2. Простота отладки — состояние не меняется после создания
  3. Кэширование — можно безопасно кэшировать и переиспользовать
  4. Hashmap ключи — безопасно использовать как ключи в Map
  5. Функциональное программирование — хорошо работают с функциональным стилем

Заключение

Преобразование класса в immutable — важная техника в Java. Выбор метода зависит от версии Java и требований проекта. Для современных версий (Java 16+) рекомендую использовать Record, а для более старых — Builder паттерн или ручное преобразование с Collections.unmodifiableList().

Как можно преобразовать обычный класс в Immutable класс? | PrepBro