Записи record
Представьте бланк заявления с графами «имя», «дата» и «подпись». Заполненный и сданный бланк никто не правит: если нужно что-то изменить, заполняют новый. Зато два бланка можно сравнить по графам: если в каждой графе написано одно и то же, заявления одинаковые.
Вспомните класс Point из темы о классах. Чтобы точку можно было создать сразу с координатами, напечатать и сравнить с другой, в класс пришлось бы дописать конструктор, методы для чтения полей, toString и метод сравнения. Это десятки строк, хотя сама точка — всего два числа. Для классов, которые только хранят данные, в Java 16 появились записи:
public class Main {
public static void main(String[] args) {
Point a = new Point(3, 4);
Point b = new Point(3, 4);
System.out.println(a); // Point[x=3, y=4]
System.out.println(a.x()); // 3
System.out.println(a == b); // false
System.out.println(a.equals(b)); // true
}
}
record Point(int x, int y) {}Давайте разберём программу по шагам:
record Point(int x, int y) {}— всё объявление. После имени в скобках перечислены компоненты — графы бланка, а тело в фигурных скобках пустое.new Point(3, 4)вызывает конструктор, хотя его никто не писал.println(a)печатаетPoint[x=3, y=4]: имя записи и каждую графу.a.x()читает компонентx. Это метод, и называется он как компонент, безget.a == bдаётfalse: это два разных объекта, два отдельных бланка.a.equals(b)даётtrue: записи сравниваются по компонентам, и в обеих лежат3и4.
Что пишет компилятор
Запись — класс, в котором вы объявляете только данные, а остальное за вас пишет компилятор. Строка record Point(int x, int y) {} даёт:
- поля
private final int xиprivate final int y; - конструктор с параметрами
xиyв том же порядке; - методы чтения
x()иy(); toStringв видеPoint[x=3, y=4];equals, который сравнивает компоненты по очереди, иhashCode, который даёт одинаковое число равным записям.
Этот вопрос часто задают на собеседовании: чем запись отличается от обычного класса? Ответ: запись хранит неизменяемые данные, а конструктор, методы чтения, equals, hashCode и toString для неё пишет компилятор.
Запись может реализовать интерфейс, как обычный класс. Но наследовать другой класс она не может, и сама она неявно final, поэтому наследовать от неё нельзя.
Запись нельзя изменить
Поля записи final: после конструктора их не меняет никто, бланк сдан. Чтобы «изменить» точку, создают новую:
Point p = new Point(3, 4);
p = new Point(5, p.y());
System.out.println(p); // Point[x=5, y=4]Вторая строка строит новую запись: x в ней равен 5, а y перенесён из старой через p.y(). Переменная p теперь ведёт к новому объекту, а старая точка осталась прежней.
Здесь картинка с бланком неточна. Если компонент записи — массив, в графе лежит не ряд шкафчиков, а талон с его адресом. Талон заменить нельзя, а вот содержимое шкафчиков поменять можно. Неизменяемы только сами графы.
Компактный конструктор
Иногда данные нужно проверить или поправить, пока бланк ещё не сдан. Для этого в записи пишут компактный конструктор — без списка параметров, только имя записи и тело:
public class Main {
public static void main(String[] args) {
System.out.println(new Range(5, 1)); // Range[from=1, to=5]
}
}
record Range(int from, int to) {
Range {
if (from > to) {
int t = from;
from = to;
to = t;
}
}
}Разберём, как он работает:
Range { … }— компактный конструктор: его параметрыfromиtoте же, что у записи.- Если
fromбольшеto, тело меняет параметры местами через переменнуюt. - После тела компилятор сам добавляет присваивания
this.from = from;иthis.to = to;, поэтому в поля попадают уже исправленные значения. new Range(5, 1)печатается какRange[from=1, to=5].
Частая ошибка
Координату записи попытались поменять:
Point p = new Point(3, 4);
p.x = 5; // ✗error: x has private access in PointПрограмма не компилируется. Поля записи закрыты словом private, поэтому снаружи их читают только через метод p.x(). Внутри записи присвоить полю тоже не выйдет: оно final, и компилятор ответит cannot assign a value to final variable x. Чтобы получить точку с другим x, создайте новую запись, как в разделе выше.
Главное
record Point(int x, int y) {}объявляет данные, а конструктор, методыx()иy(),toString,equalsиhashCodeпишет компилятор.equalsзаписи сравнивает компоненты: две записи с одинаковыми данными равны, хотя==для них даётfalse.- Поля записи
private final: запись не меняют, а создают новую. - Компактный конструктор проверяет или поправляет значения до того, как они попадут в поля.