1. Розширення record-класу: додаткові методи
Чи можна додати методи до record?
Звісно! Record — це як квартира з готовим ремонтом: стіни й підлога вже зроблені, змінювати їх не можна, але меблі розставити по-своєму ніхто не забороняє. Усередині record можна оголошувати звичайні методи, статичні методи й навіть зберігати константи. Це означає, що бізнес-логіку не обовʼязково виносити в окремі «утилітні» класи — її можна акуратно вбудувати прямо в сам record.
Приклад: метод обчислення відстані між точками
Припустімо, у нас є record для точки на площині:
public record Point(int x, int y) {
// Додатковий метод
public double distanceTo(Point other) {
int dx = this.x - other.x;
int dy = this.y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
}
Тепер можна робити так:
Point p1 = new Point(0, 0);
Point p2 = new Point(3, 4);
System.out.println(p1.distanceTo(p2)); // 5.0
Як бачите, record-клас можна «збагачувати» власними методами — і це дуже зручно!
Приклад: статичний метод
public record Rectangle(int width, int height) {
public int area() {
return width * height;
}
public static Rectangle square(int size) {
return new Rectangle(size, size);
}
}
Тепер можна створювати «квадрат» одним викликом:
Rectangle r = Rectangle.square(5);
System.out.println(r.area()); // 25
2. Компактний конструктор і валідація даних
Навіщо потрібен «компактний» конструктор?
Канонічний конструктор record створюється автоматично та присвоює значення параметрів полям. Але іноді хочеться додати перевірку вхідних даних (наприклад, заборонити відʼємні координати).
У звичайному класі ми б написали:
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
if (x < 0 || y < 0) throw new IllegalArgumentException();
this.x = x;
this.y = y;
}
// ...
}
У record-класі можна оголосити компактний конструктор — без повторення списку параметрів і без явного присвоєння полів (це робить компілятор за нас).
Синтаксис компактного конструктора
public record Point(int x, int y) {
public Point {
if (x < 0 || y < 0) {
throw new IllegalArgumentException("Coordinates must be non-negative");
}
// Не потрібно писати: this.x = x; this.y = y;
}
}
- Параметри конструктора автоматично збігаються з компонентами record.
- Присвоєння this.x = x і this.y = y відбувається компілятором автоматично після виконання тіла конструктора (або після успішного виходу з нього).
- Якщо викинути виняток, обʼєкт не буде створено.
Приклад із перевіркою
Point p1 = new Point(3, 5); // OK
Point p2 = new Point(-1, 2); // Викине IllegalArgumentException!
Чи можна оголосити «звичайний» конструктор?
Так, можна! Якщо потрібно створити конструктор із іншим списком параметрів або додатковою логікою, оголошуйте його явно:
public record Range(int from, int to) {
public Range(int size) {
this(0, size); // викликає канонічний конструктор
}
}
3. Обмеження record-класів
На відміну від звичайних класів, record-класи мають низку обмежень. Важливо про це памʼятати, щоб не дивуватися помилкам компіляції.
Лише компоненти — жодних додаткових нестатичних полів
У record-класі не можна оголошувати нові нестатичні поля:
public record Person(String name, int age) {
// int id; // Помилка компіляції! Не можна додавати нестатичні поля.
}
Можна оголошувати статичні поля та методи:
public record Person(String name, int age) {
public static final String SPECIES = "Homo sapiens";
}
Record завжди final
Record-клас не може бути батьківським (не можна від нього наслідуватися) і сам не може явно наслідувати інший клас (окрім неявного наслідування від java.lang.Record). Це означає, що record-клас — завжди «кінцева» структура.
public record User(String login) { }
// public class Admin extends User {} // Помилка: не можна наслідуватися від record!
Можна реалізовувати інтерфейси
Record-клас може реалізовувати інтерфейси:
public interface Printable {
void print();
}
public record Invoice(int amount) implements Printable {
@Override
public void print() {
System.out.println("Сума: " + amount);
}
}
4. Приклади: розширені records у реальних задачах
Розгляньмо кілька практичних прикладів, де додаткові методи та компактні конструктори роблять record-клас справді корисним.
Record із обчислюваним методом
public record Circle(double x, double y, double radius) {
public double area() {
return Math.PI * radius * radius;
}
public double distanceTo(Circle other) {
double dx = x - other.x;
double dy = y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
}
Record із валідацією
public record Email(String value) {
public Email {
if (value == null || !value.contains("@")) {
throw new IllegalArgumentException("Некоректний email: " + value);
}
}
}
Тепер не можна створити некоректний email:
Email e1 = new Email("test@example.com"); // OK
Email e2 = new Email("not-an-email"); // Кине IllegalArgumentException
Record із додатковими статичними методами
public record Temperature(double celsius) {
public static Temperature fromFahrenheit(double fahrenheit) {
return new Temperature((fahrenheit - 32) * 5 / 9);
}
public double toFahrenheit() {
return celsius * 9 / 5 + 32;
}
}
Використання:
Temperature t = Temperature.fromFahrenheit(98.6);
System.out.println(t.celsius()); // 37.0
System.out.println(t.toFahrenheit()); // 98.6
5. Компактний конструктор: нюанси й обмеження
Коли використовувати компактний конструктор?
- Якщо потрібно перевірити коректність даних (валідація).
- Якщо потрібно змінити значення перед збереженням (наприклад, округлити число або перевести рядок у верхній регістр).
- Якщо хочеться уникнути дублювання списку параметрів.
Особливості роботи
- У компактному конструкторі не можна явно присвоювати значення компонентам (this.x = ...) — це спричинить помилку компіляції, адже компілятор сам виконує присвоєння після виконання тіла конструктора.
- У компактному конструкторі не можна змінювати імена параметрів — вони завжди збігаються з іменами компонентів record.
Приклад: автоматичне округлення
public record Money(double amount) {
public Money {
amount = Math.round(amount * 100) / 100.0; // Округлюємо до копійок
}
}
6. Практика: розвиваємо навчальний застосунок
Припустімо, ми робимо банківські операції у навчальному застосунку. Нехай у нас є record-клас Transaction, який зберігає суму, відправника та одержувача.
public record Transaction(String from, String to, double amount) {
public Transaction {
if (amount <= 0) throw new IllegalArgumentException("Сума має бути додатною");
if (from == null || to == null) throw new IllegalArgumentException("Поля не можуть бути null");
}
public String description() {
return String.format("Переказ %.2f від %s до %s", amount, from, to);
}
}
Використання:
Transaction t = new Transaction("Alice", "Bob", 150.0);
System.out.println(t.description()); // Переказ 150.00 від Alice до Bob
Спроба створити некоректну транзакцію спричинить помилку:
Transaction t2 = new Transaction("Alice", "Bob", -10.0); // IllegalArgumentException
Таблиця: що можна і що не можна у record-класі
| Можна в record-класі | Не можна в record-класі |
|---|---|
| Звичайні методи | Нові нестатичні поля |
| Статичні методи й поля | Наслідуватися від інших класів |
| Реалізовувати інтерфейси | Бути суперкласом для інших |
| Компактні та звичайні конструктори | Змінювати компоненти після створення |
| Перевизначати методи | Використовувати сетери |
7. Типові помилки під час роботи з record-класами з нестандартним тілом
Помилка № 1: спроба додати нестатичне поле.
Початківці часто намагаються додати до record-класу «ще одне поле для внутрішньої логіки» — наприклад, лічильник або кеш. Це не спрацює: компілятор одразу видасть помилку. Якщо вам потрібно зберігати додатковий стан, швидше за все, record-клас — не ваш вибір.
Помилка № 2: забули валідацію в компактному конструкторі.
Якщо ви хочете, щоб обʼєкт завжди був валідним, робіть перевірку в компактному конструкторі. Не покладайтеся на те, що «користувач сам не введе нісенітницю».
Помилка № 3: спроба змінити компонент після створення.
Поля record-класу — final, тому їх не можна змінити ані напряму, ані через методи. Якщо вам потрібна змінювана структура — використовуйте звичайний клас.
Помилка № 4: дублювання логіки в методах і конструкторі.
Іноді логіку перевірки та логіку обчислень намагаються дублювати й у методах, і в конструкторі. Краще робити всю валідацію в конструкторі, а методи залишати для «чистої» бізнес-логіки.
Помилка № 5: забули про обмеження наслідування.
Record-клас завжди final — не можна створити від нього нащадка. Якщо ви проєктуєте ієрархію, де потрібні підкласи, — використовуйте звичайні класи.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ