JavaRush /Курсы /JAVA 25 SELF /Immutability — неизменяемость record-классов

Immutability — неизменяемость record-классов

JAVA 25 SELF
22 уровень, 1 лекция
Открыта

1. Record-классы и неизменяемость

Неизменяемость — это свойство объекта, при котором его состояние не может быть изменено после создания. Другими словами: если объект создан, вы не можете поменять его внутренние данные. Всё! Как в камне высечено.

Представьте билет на поезд. Как только он напечатан, вы не можете изменить дату или место отправления (ну, если не брать в расчёт хитрые махинации с фотошопом). Билет — неизменяемый объект. Если вы хотите другой билет — покупаете новый.

В программировании такие объекты называют immutable objects. Они защищают программу от случайных изменений, делают код более безопасным и предсказуемым.

Признаки неизменяемого объекта

  • Все поля объекта — final (их можно присвоить только один раз, обычно в конструкторе).
  • Нет сеттеров (методов, которые меняют значения полей).
  • Все методы, возвращающие внутренние данные, либо возвращают копии, либо сами данные тоже неизменяемы.

Record-классы в Java были придуманы именно для того, чтобы создавать неизменяемые структуры данных просто и без боли.

Почему record неизменяемый?

  • Все компоненты record — final.
    Когда вы объявляете record, компилятор автоматически делает все его поля private final. Это значит, что после создания объекта вы не сможете изменить его поля.
  • Нет сеттеров.
    В классе-record нельзя добавить метод setX(int x) — компилятор не позволит вам изменить значение поля после создания объекта.
  • Конструктор присваивает значения только один раз.
    Все значения задаются только в момент создания объекта.

Пример

public record Point(int x, int y) {}

Point p = new Point(5, 10);
// p.x = 7; // Ошибка компиляции: поле x имеет private-доступ и является final
// p.x(7);  // Ошибка: нет сеттера!
System.out.println(p.x()); // 5

Попытка изменить поле или вызвать несуществующий сеттер приводит к ошибке компиляции. Java строго следит за контрактом неизменяемости.

2. Преимущества неизменяемых объектов

Безопасность в многопоточности

В многопоточных программах (а таких сейчас большинство!) неизменяемые объекты — это как бронежилет. Если объект нельзя изменить, разные потоки могут его спокойно читать, не боясь, что кто-то в этот момент поменяет данные. Не нужно синхронизировать доступ и переживать из-за гонок данных.

Факт: многие классы в стандартной библиотеке Java, которые активно используются в многопоточных сценариях, либо неизменяемы, либо специально защищены от изменений.

Упрощение понимания кода

Если объект неизменяем, вы всегда уверены: передали его в другой метод или класс — и он не изменится «за вашей спиной». Это сильно облегчает чтение и отладку кода. Не нужно гадать, кто и где мог поменять поле, — никто не мог!

Удобно использовать как ключи в коллекциях

Неизменяемые объекты отлично подходят для использования в качестве ключей в коллекциях вроде HashMap или HashSet. Почему? Потому что их equals и hashCode зависят только от полей, которые не меняются. Значит, объект не «потеряется» в коллекции из-за того, что его состояние изменилось.

Меньше скрытых багов

Изменяемый объект легко испортить, случайно передав ссылку не туда. Неизменяемый же объект — как печатная книга: никто не может вырвать или переписать страницу.

3. Сравнение с обычными классами

Давайте сравним поведение обычного класса и record-класса. Для примера возьмём простую модель точки на плоскости.

Обычный класс (mutable)

public class PointClass {
    private int x;
    private int y;

    public PointClass(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() { 
        return x; 
    }
    public int getY() { 
        return y; 
    }

    public void setX(int x) { 
        this.x = x; 
    }
    public void setY(int y) { 
        this.y = y; 
    }
}

Можно создать объект и потом сколько угодно менять его состояние:

PointClass p = new PointClass(1, 2);
p.setX(10); // p.x теперь 10

Record-класс (immutable)

public record Point(int x, int y) {}

Создаёте объект — и всё, он навсегда такой, каким был создан:

Point p = new Point(1, 2);
// p.x = 10;   // Ошибка! Нет доступа к полю
// p.x(10);    // Ошибка! Нет сеттера

Таблица: сравнение поведения

Обычный класс Record-класс
Поля любые только private final
Сеттеры можно добавить нельзя добавить
Изменяемость изменяемый (mutable) неизменяемый
Автогенерация нет да (equals, hashCode, toString)

4. Практика: как использовать неизменяемость record-классов

Давайте попробуем применить record-класс в небольшом примере. Пусть у нас есть банковское приложение, и мы хотим хранить информацию о транзакции:

public record Transaction(String fromAccount, String toAccount, double amount) {}

Создаём объект:

Transaction t = new Transaction("12345", "67890", 1500.0);
System.out.println(t);
// Transaction[fromAccount=12345, toAccount=67890, amount=1500.0]

Попробуем "перевести" деньги на другой счёт:

// t.toAccount = "11111"; // Ошибка! Поле final, доступа нет
// t.toAccount("11111");  // Ошибка! Нет сеттера

Если нам нужна другая транзакция — создаём новый объект:

Transaction t2 = new Transaction(t.fromAccount(), "11111", t.amount());

Важно: неизменяемость не значит «неудобно». Это просто другой стиль работы: если нужно новое состояние — создаём новый объект.

5. Особенности неизменяемости: что нужно помнить

Неизменяемость — не всегда абсолютна!

Record-класс гарантирует, что его поля не изменятся. Но если поле — это ссылка на изменяемый объект (например, массив или обычный класс), то содержимое этого объекта может быть изменено.

Пример с массивом

public record DataHolder(int[] data) {}

int[] arr = {1, 2, 3};
DataHolder holder = new DataHolder(arr);
arr[0] = 99;
System.out.println(holder.data()[0]); // 99! Массив изменился

Вывод: если хотите настоящую неизменяемость, используйте только неизменяемые типы (String, Integer, другие record’ы и т.д.) или делайте копии изменяемых объектов внутри канонического конструктора record-класса. Например:

int[] copy = Arrays.copyOf(data, data.length);

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

Если вы хотите сделать обычный класс immutable (неизменяемым), придётся вручную:

  • Пометить все поля как private final,
  • Не добавлять сеттеров,
  • Все поля инициализировать только через конструктор,
  • Если поле — изменяемый объект, делать защитную копию (defensive copy).

Пример

public final class User {
    private final String name;
    private final int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String name() { 
        return name; 
    }
    public int age() { 
        return age; 
    }
}

Согласитесь, с record-классом это делается проще и короче:

public record User(String name, int age) {}

7. Типичные ошибки при работе с неизменяемыми record-классами

Ошибка №1: попытка изменить поле после создания.
Новички часто пытаются написать p.x = 42; или p.x(42); для record-объекта. Но компилятор сразу скажет: «Так нельзя! Поле final, сеттера нет».

Ошибка №2: использование изменяемых объектов как компонентов record.
Если вы добавили в record поле типа List, Map, массив или другой изменяемый объект, то сам record не защитит вас от изменений содержимого этого объекта. Например, если у вас есть record User(List<String> hobbies), то кто-то может добавить или удалить элемент из списка, и это изменит состояние вашего record-объекта. Чтобы этого избежать, используйте неизменяемые коллекции (List.copyOf, Collections.unmodifiableList) или делайте копии коллекций внутри конструктора record.

Ошибка №3: неверное понимание неизменяемости.
Некоторые думают, что если объект — record, то он защищён от любых изменений. На самом деле, если поля — ссылки на изменяемые объекты, их содержимое можно поменять, и это приведёт к неожиданным багам.

1
Задача
JAVA 25 SELF, 22 уровень, 1 лекция
Недоступна
Билет, который нельзя переписать 🎫
Билет, который нельзя переписать 🎫
1
Задача
JAVA 25 SELF, 22 уровень, 1 лекция
Недоступна
Данные исторических личностей 🗿
Данные исторических личностей 🗿
1
Задача
JAVA 25 SELF, 22 уровень, 1 лекция
Недоступна
Перемещение объекта на игровой карте 🎮
Перемещение объекта на игровой карте 🎮
1
Задача
JAVA 25 SELF, 22 уровень, 1 лекция
Недоступна
Отслеживание показаний датчиков 📊
Отслеживание показаний датчиков 📊
Комментарии (4)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Grigory Alekseev Уровень 25
1 октября 2026
" или делайте копии коллекций внутри конструктора record." Т.е. можно переопределить конструктор recordа или это про вызов конструктора?
Олег Уровень 40
19 мая 2026
Сложность задач вызвала неудовлетворение
Cherepoq Уровень 26
24 марта 2026
"Можно создать объект и потом сколько угодно менять его состояние: PointClass p = new PointClass(1, 2); p.setX(10); // p.x теперь 10 Record-класс (immutable) public record Point(int x, int y) {} Создаёте объект — и всё, он навсегда такой, каким был создан: Point p = new Point(1, 2); // p.x = 10; // Ошибка! Нет доступа к полю // p.x(10); // Ошибка! Нет сеттера" Это такой бред, ну не пиши сеттер и доступа к нему не будет. Поля сделай final и тот же рекорд будет. Хочешь быстро есть сочетания клавиш 2 поля объявил пару кнопок нажал и тот же самый код. Самое странное это пример-подтверждение просто поля, которое можно сделать final в не рекорде. Как с абстракцией короче, 1 добавочный уровень для раздувания
Ioanna Polyak Уровень 51
6 ноября 2025
23🤣?