Які методи автоматично генеруються для 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]]