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 — нельзя создать от него наследника. Если вы проектируете иерархию, где нужны подклассы — используйте обычные классы.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ