JavaRush /Курси /JAVA 25 SELF /Record із методами

Record із методами

JAVA 25 SELF
Рівень 22 , Лекція 3
Відкрита

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 — не можна створити від нього нащадка. Якщо ви проєктуєте ієрархію, де потрібні підкласи, — використовуйте звичайні класи.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ