Вопрос 9 · Раздел 20

Являются ли поля Record финальными

Это делает Record immutable (неизменяемым) по дизайну.

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

Да, все поля в Record автоматически являются final. Это значит, что их нельзя изменить после создания объекта.

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

User user = new User("John", 25);

// ❌ Нельзя изменить поля
user.name = "Jane";  // compilation error — поле final

// ✅ Можно создать новый Record
user = new User("Jane", 25);  // OK

Это делает Record immutable (неизменяемым) по дизайну.


🟡 Middle Level

Как это работает

Компилятор автоматически делает все поля private final:

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

// Компилятор генерирует:
public final class Point extends java.lang.Record {
    private final int x;  // implicit final
    private final int y;  // implicit final
    
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    
    public int x() { return x; }
    public int y() { return y; }
}

Даже если вы не написали final:

// Оба варианта одинаковы
public record Point(int x, int y) {}
public record Point(final int x, final int y) {}  // final избыточен

Важный нюанс: mutable компоненты

Поля final, но если они ссылаются на mutable объекты, то содержимое можно изменить:

public record User(String name, List<String> tags) {}

User user = new User("John", new ArrayList<>(List.of("admin", "user")));

// ❌ Нельзя заменить список
// user.tags = new ArrayList<>();  // compilation error

// ✅ Но можно изменить содержимое списка
user.tags().add("moderator");  // OK! Список mutable
user.tags().clear();           // OK!

Решение — defensive copy:

public record User(String name, List<String> tags) {
    public User {
        tags = List.copyOf(tags);  // immutable список
    }
}

User user = new User("John", List.of("admin"));
// user.tags().add("moderator");  // UnsupportedOperationException!

Типичные ошибки

  1. Ожидание полной иммутабельности: ```java public record Data(int[] values) {}

Data d = new Data(new int[]{1, 2, 3}); d.values()[0] = 99; // ✅ Можно — массив mutable!

// ✅ Решение public record SafeData(List values) { public SafeData { values = List.copyOf(values); } }


2. **Попытка изменить поле:**
```java
public record Point(int x, int y) {
    public void setX(int x) {
        // this.x = x;  // compilation error — final поле
    }
}

🔴 Senior Level

Internal Implementation

JVM level:

// Все компоненты Record имеют ACC_FINAL флаг
Field {
    access_flags: ACC_PRIVATE | ACC_FINAL
    name: "x"
    descriptor: "I"
}

Reflection подтверждает:

Field[] fields = Point.class.getDeclaredFields();
for (Field f : fields) {
    System.out.println(f.getName() + " final=" + Modifier.isFinal(f.getModifiers()));
}
// Вывод: x final=true, y final=true

Архитектурные Trade-offs

Final поля:

Плюсы Минусы
Thread-safe (safe publication) Нельзя изменить состояние
JIT оптимизации Нужно создавать новые объекты
Предсказуемость Mutable компоненты требуют защиты
HashMap ключи Сложнее работать с графами

Edge Cases

1. Mutable компоненты:

public record MutableRecord(Date createdAt) {}

MutableRecord r = new MutableRecord(new Date());
r.createdAt().setTime(0);  // можно изменить Date!

// ✅ Решение
public record ImmutableRecord(Date createdAt) {
    public ImmutableRecord {
        createdAt = new Date(createdAt.getTime());  // defensive copy
    }
}

2. Final ≠ immutable:

public record Config(Map<String, String> props) {}

// final поле, но Map можно изменить
Config c = new Config(new HashMap<>());
c.props().put("key", "value");  // OK

3. Memory visibility:

// final поля гарантируют safe publication
// после завершения конструктора все потоки видят корректные значения
public record SharedConfig(String url, int timeout) {}

// Publication safe
SharedConfig config = new SharedConfig("http://api.com", 5000);
// Другой поток увидит корректные значения

Производительность

Final поля:
- JIT может инлайнить значения
- Нет необходимости в volatile
- Safe publication без синхронизации
- Memory barrier при конструировании

Бенчмарк:

// Примерные значения. Зависят от JVM/hardware.
Операция           | Final поля | Mutable поля
-------------------|------------|-------------
Read                | 1 ns       | 1 ns
Publish (thread)    | 0 ns       | sync needed
JIT optimization    | Максимальная | Ограниченная

Production Experience

Immutable DTO:

public record OrderDto(
    String id,
    Instant createdAt,
    BigDecimal amount,
    List<OrderItem> items
) {
    public OrderDto {
        items = List.copyOf(items);  // защита mutable коллекции
    }
}

Value objects:

public record Money(BigDecimal amount, Currency currency) {
    // Оба поля final — полностью immutable
    public Money add(Money other) {
        // Возвращаем новый объект, не меняем текущий
        return new Money(this.amount.add(other.amount), this.currency);
    }
}

Best Practices

// ✅ Record для immutable данных
public record User(String name, Email email) {}

// ✅ Defensive copy для mutable компонентов
public record Tags(List<String> values) {
    public Tags { values = List.copyOf(values); }
}

// ✅ Immutable коллекции
public record Config(Map<String, String> props) {
    public Config { props = Map.copyOf(props); }
}

// ❌ Mutable компоненты без защиты
public record BadRecord(Date date, int[] array) {}
// ❌ Ожидание что final = полностью immutable

🎯 Шпаргалка для интервью

Обязательно знать:

  • Все поля Record автоматически private final — компилятор добавляет final
  • Final означает нельзя заменить ссылку, но mutable объекты внутри можно изменить
  • Массивы, Date, коллекции — mutable компоненты, требуют defensive copy
  • List.copyOf(), Set.copyOf(), Map.copyOf() для защиты mutable коллекций
  • Final поля гарантируют safe publication в многопоточной среде (без volatile)
  • JIT может лучше оптимизировать final поля (inlining)

Частые уточняющие вопросы:

  • Можно ли изменить массив внутри Record? — Да, массив mutable: record.values()[0] = 99
  • Как защитить mutable компонент? — Defensive copy в компактном конструкторе: tags = List.copyOf(tags)
  • Final = полностью immutable? — Нет, final защищает только ссылку, не содержимое объекта
  • Почему final важен для HashMap ключей? — Изменённый ключ = сломанный hashCode, данные потеряны

Красные флаги (НЕ говорить):

  • ❌ “Final означает полную иммутабельность” — Mutable компоненты можно изменить
  • ❌ “Массив в Record безопасен” — Массивы всегда mutable, нужен defensive copy
  • ❌ “Можно изменить поле через setter” — Поля final, setter невозможен
  • ❌ “Final не даёт преимуществ” — Final даёт safe publication и JIT оптимизации

Связанные темы:

  • [[1. Что такое Record в Java и с какой версии они доступны]]
  • [[5. Какие методы автоматически генерируются для Record]]
  • [[7. Что такое компактный конструктор (compact constructor) в Record]]
  • [[10. Можно ли использовать Record как ключ в HashMap]]