Какие методы автоматически генерируются для Record
Когда вы создаёте Record, компилятор автоматически генерирует 5 типов методов:
🟢 Junior Level
Когда вы создаёте Record, компилятор автоматически генерирует 5 типов методов:
- Конструктор — принимает все поля в порядке объявления
- Геттеры — для каждого поля (без приставки
get) - equals() — сравнивает все поля
- hashCode() — хеш всех полей
- toString() — строковое представление
public record User(String name, int age) {}
// Автогенерированные методы:
User user = new User("John", 25); // конструктор
user.name(); // "John" — геттер для name
user.age(); // 25 — геттер для age
user.equals(other); // сравнивает name и age
user.hashCode(); // хеш от name и age
user.toString(); // "User[name=John, age=25]"
🟡 Middle Level
Как это работает
1. Канонический конструктор:
public record User(String name, int age) {}
// Автогенерированный конструктор:
public User(String name, int age) {
this.name = name;
this.age = age;
}
2. Аксессоры (геттеры):
// Имя метода = имя поля (НЕ getName()!)
public String name() { return name; }
public int age() { return age; }
3. equals() — сравнивает все компоненты:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User other)) return false;
return Objects.equals(name, other.name) && age == other.age;
}
4. hashCode() — от всех полей:
@Override
public int hashCode() {
// На самом деле компилятор генерирует оптимизированный hashCode:
// 31 * name.hashCode() + age (прямая арифметика, не Objects.hash).
// Objects.hash() создаёт массив и медленнее — это концептуальное упрощение.
return Objects.hash(name, age);
}
5. toString() — читаемое представление:
@Override
public String toString() {
return "User[name=" + name + ", age=" + age + "]";
}
Типичные ошибки
- Ожидание get/set: ```java User user = new User(“John”, 25);
// ❌ user.getName() — такого метода нет! // ❌ user.setName(“Jane”) — Record immutable! // ✅ user.name() — правильный вызов
2. **Попытка переопределить аксессор:**
```java
public record Point(int x, int y) {
// ❌ Нельзя изменить тип или сигнатуру аксессора
public String x() { return String.valueOf(x); } // error
}
Практическое применение
Record как ключ в HashMap:
public record CacheKey(String tenantId, String entityType, String entityId) {}
Map<CacheKey, Object> cache = new ConcurrentHashMap<>();
cache.put(new CacheKey("t1", "user", "u1"), userData);
// equals и hashCode работают корректно
CacheKey key = new CacheKey("t1", "user", "u1");
cache.get(key); // найдёт userData
🔴 Senior Level
Internal Implementation
Class file атрибуты:
// Компилятор добавляет:
- ACC_FINAL флаг для класса
- ACC_RECORD флаг
- RecordComponents attribute в constant pool
- Canonical constructor
- Accessor methods для каждого компонента
- equals, hashCode, toString из java.lang.Record (или переопределённые)
java.lang.Record базовая реализация:
// java.lang.Record предоставляет дефолтные реализации:
public abstract class Record {
protected Record() {}
@Override
public abstract boolean equals(Object obj);
@Override
public abstract int hashCode();
@Override
public abstract String toString();
}
Автогенерация equals (псевдокод):
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
Record that = (Record) other;
for (RecordComponent rc : this.getClass().getRecordComponents()) {
Object thisValue = rc.getAccessor().invoke(this);
Object thatValue = rc.getAccessor().invoke(that);
if (!Objects.equals(thisValue, thatValue)) {
return false;
}
}
return true;
}
Архитектурные Trade-offs
Автогенерация vs ручная реализация:
| Аспект | Автогенерация | Ручная реализация |
|---|---|---|
| Код | 0 строк | 20-50 строк |
| Ошибки | Нет | Возможны |
| Кастомизация | Нет | Полная |
| Производительность | Оптимальная | Зависит от реализации |
Edge Cases
1. Переопределение equals/hashCode:
public record CaseInsensitiveName(String name) {
@Override
public boolean equals(Object o) {
if (!(o instanceof CaseInsensitiveName other)) return false;
return name.equalsIgnoreCase(other.name);
}
@Override
public int hashCode() {
return name.toLowerCase().hashCode();
}
}
2. Lazy hashCode кэширование:
// Instance поля (даже volatile) нельзя добавлять в Record — это compilation error.
3. Кастомный toString:
public record SecretRecord(String publicData, String secretData) {
@Override
public String toString() {
return "SecretRecord[public=" + publicData + ", secret=***]";
}
}
Производительность
Операция | Автогенерация | Ручная реализация
-------------------|---------------|-------------------
equals() | 15 ns | 14 ns
hashCode() | 12 ns | 10 ns
toString() | 45 ns | 40 ns
Разница < 10% — автогенерация практически оптимальна
Production Experience
JPA проблема:
// ❌ Record не работает с Hibernate
// Hibernate требует:
// 1. No-arg constructor
// 2. Mutable поля для lazy loading
// 3. Proxy mechanism
@Entity
public record User(Long id, String name) {} // НЕ работает!
// ✅ Обычный класс
@Entity
public class User {
@Id private Long id;
private String name;
}
Best Practices
// ✅ Используйте автогенерацию для простых Record
public record Point(int x, int y) {} // auto equals/hashCode/toString
// ✅ Переопределяйте только при необходимости
public record Password(String hash, String salt) {
@Override
public String toString() {
return "Password[hash=***, salt=***]"; // скрываем sensitive data
}
}
// ❌ Не переопределяйте без причины
// ❌ Не меняйте контракт equals/hashCode
🎯 Шпаргалка для интервью
Обязательно знать:
- Record автогенерирует 5 типов: канонический конструктор, аксессоры, equals(), hashCode(), toString()
- Аксессоры без
get:name()вместоgetName() - equals() сравнивает все компоненты через
Objects.equals() - hashCode() вычисляется от всех компонентов (оптимизированная цепочка, не Objects.hash)
- toString() формат:
RecordName[field1=value1, field2=value2] - Можно переопределить любой из методов при необходимости
Частые уточняющие вопросы:
- Можно ли отключить автогенерацию equals? — Нет, но можно переопределить своей реализацией
- Как hashCode вычисляется? — Оптимизированная цепочка умножений (31 * h1 + h2…), не Objects.hash
- Что если переопределить equals но не hashCode? — Нарушение контракта HashMap — баги!
- Можно ли переопределить только toString? — Да, часто делают для скрытия sensitive data
Красные флаги (НЕ говорить):
- ❌ “equals() использует ==” — equals() сравнивает значения всех полей через Objects.equals
- ❌ “hashCode() кэшируется” — Нет, вычисляется каждый раз (нельзя добавить field для кэша)
- ❌ “Record генерирует геттеры с get” — Аксессоры без get:
name()неgetName() - ❌ “toString() скрывает данные” — toString() показывает все поля, нужно переопределить для sensitive data
Связанные темы:
- [[1. Что такое Record в Java и с какой версии они доступны]]
- [[2. В чём основные отличия Record от обычного класса]]
- [[6. Можно ли переопределить конструктор в Record]]
- [[10. Можно ли использовать Record как ключ в HashMap]]