Records (Java 14+)
Record — это специальный тип класса для хранения иммутабельных данных. Предложен в Java 14 как preview-фича (JEP 359) и стабилизирован в Java 16 (JEP 395). Records радикально сокращают boilerplate: одна строка заменяет 40+ строк класса с полями, конструктором, геттерами, equals, hashCode и toString.
По сути record — это «прозрачный носитель» неизменяемых данных (transparent carrier for immutable data). Компилятор сам генерирует все стандартные методы.
Минимальная версия: Java 14 (preview), стабильно с Java 16.
Синтаксис
public record ИмяРекорда(Тип1 поле1, Тип2 поле2, ...) { }
Что компилятор генерирует автоматически:
приватные
finalполя;публичный конструктор с теми же параметрами (canonical constructor);
публичные методы доступа с именами полей (
поле1(), неgetПоле1());equals(),hashCode(),toString().
Зачем нужно
Меньше кода — меньше ошибок.
Иммутабельность по умолчанию — безопасно для многопоточности.
Чёткая семантика: «это данные, а не поведение».
Отлично подходит для DTO, ключей map, value-объектов.
Пример 1. Базовый record
public class RecordsBasic {
public record Point(int x, int y) { }
public static void main(String[] args) {
var p = new Point(3, 7);
System.out.println("x = " + p.x());
System.out.println("y = " + p.y());
System.out.println(p);
}
}
Output:
x = 3
y = 7
Point[x=3, y=7]
Пример 2. equals и hashCode из коробки
import java.util.HashSet;
public class RecordsEquals {
public record Sensor(String name, int pin) { }
public static void main(String[] args) {
var a = new Sensor("trig", 3);
var b = new Sensor("trig", 3);
var c = new Sensor("echo", 7);
System.out.println("a.equals(b) = " + a.equals(b));
System.out.println("a.equals(c) = " + a.equals(c));
var set = new HashSet<Sensor>();
set.add(a);
set.add(b);
set.add(c);
System.out.println("Размер множества: " + set.size());
}
}
Output:
a.equals(b) = true
a.equals(c) = false
Размер множества: 2
Пример 3. Компактный конструктор с валидацией
public class RecordsCompact {
public record Pin(int number) {
// Компактный конструктор: без списка параметров
public Pin {
if (number < 0 || number > 19) {
throw new IllegalArgumentException("Pin вне диапазона: " + number);
}
}
}
public static void main(String[] args) {
var ok = new Pin(13);
System.out.println("OK: " + ok);
try {
var bad = new Pin(42);
} catch (IllegalArgumentException e) {
System.out.println("Ошибка: " + e.getMessage());
}
}
}
Output:
OK: Pin[number=13]
Ошибка: Pin вне диапазона: 42
Пример 4. Дополнительные методы и static
public class RecordsMethods {
public record Motor(int in1, int in2, int pwm) {
public static Motor leftRobotPhobo() {
return new Motor(8, 12, 6);
}
public static Motor rightRobotPhobo() {
return new Motor(4, 2, 5);
}
public String describe() {
return "Motor[in1=" + in1 + ", in2=" + in2 + ", pwm=" + pwm + "]";
}
}
public static void main(String[] args) {
var left = Motor.leftRobotPhobo();
var right = Motor.rightRobotPhobo();
System.out.println("Left: " + left.describe());
System.out.println("Right: " + right.describe());
}
}
Output:
Left: Motor[in1=8, in2=12, pwm=6]
Right: Motor[in1=4, in2=2, pwm=5]
Пример 5. Реализация интерфейса
public class RecordsInterface {
interface Named {
String name();
}
public record Robot(String name, int wheels) implements Named { }
public static void main(String[] args) {
Named n = new Robot("Phobo", 4);
System.out.println("Имя: " + n.name());
System.out.println("Объект: " + n);
}
}
Output:
Имя: Phobo
Объект: Robot[name=Phobo, wheels=4]
Пример 6. Локальный record (Java 16+)
import java.util.List;
public class RecordsLocal {
public static void main(String[] args) {
// record можно объявить прямо внутри метода
record Reading(String sensor, double value) { }
var readings = List.of(
new Reading("ultrasonic", 12.4),
new Reading("line-left", 540.0),
new Reading("line-right", 612.0)
);
for (var r : readings) {
System.out.println(r.sensor() + " = " + r.value());
}
}
}
Output:
ultrasonic = 12.4
line-left = 540.0
line-right = 612.0
Подводные камни
Предупреждение
Records неявно final — наследовать от них нельзя.
Все поля final — модифицировать их после создания нельзя (но если поле — изменяемый объект, например
List, его содержимое менять можно; для полной иммутабельности используйтеList.copyOf).Records не могут наследоваться от других классов, но могут реализовать интерфейсы.
Нельзя добавить дополнительные instance-поля вне списка компонентов.
Геттеры называются
field(), а неgetField()— это ломает совместимость с фреймворками, ожидающими JavaBeans.
Совет
Если объект изменчив или имеет сложное поведение — это не record, а обычный класс. Record предназначен именно для данных.
См. также
Примечание
Материал основан на официальной документации Oracle Java SE (docs.oracle.com/en/java/javase) и спецификациях JEP 359/395 (openjdk.org/jeps/395), распространяемой под лицензией Oracle Free Documentation License. Тексты и примеры написаны заново для AlashEd Wiki.