1. Введение
Передача неизменяемых данных между объектами — одна из самых распространенных, но рутинных задач во многих приложениях Java.
До Java 14 это требовало создания класса с шаблонными полями и методами, которые были подвержены тривиальным ошибкам и путаным намерениям.
С выпуском Java 14 мы теперь можем использовать записи для решения этих проблем.
В этом руководстве мы рассмотрим основы записей , включая их назначение, сгенерированные методы и методы настройки .
2. Цель
Обычно мы пишем классы, чтобы просто хранить данные, такие как результаты базы данных, результаты запросов или информацию из службы.
Во многих случаях эти данные являются неизменяемыми , поскольку неизменность обеспечивает достоверность данных без синхронизации .
Для этого мы создаем классы данных со следующим:
private
,конечное
поле для каждого фрагмента данных- геттер для каждого поля
публичный
конструктор с соответствующим аргументом для каждого поляequals
, который возвращаетtrue
для объектов одного класса, когда все поля совпадают- `
метод
hashCode , который возвращает одно и то же значение, когда все поля совпадают` метод toString
, который включает имя класса, имя каждого поля и соответствующее ему значение.
Например, мы можем создать простой класс данных Person
с именем и адресом:
public class Person {
private final String name;
private final String address;
public Person(String name, String address) {
this.name = name;
this.address = address;
}
@Override
public int hashCode() {
return Objects.hash(name, address);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (!(obj instanceof Person)) {
return false;
} else {
Person other = (Person) obj;
return Objects.equals(name, other.name)
&& Objects.equals(address, other.address);
}
}
@Override
public String toString() {
return "Person [name=" + name + ", address=" + address + "]";
}
// standard getters
}
Хотя это достигает нашей цели, есть две проблемы:
- Там много стандартного кода
- Мы затемняем цель нашего класса: представлять человека с именем и адресом
В первом случае нам приходится повторять один и тот же утомительный процесс для каждого класса данных, монотонно создавая новое поле для каждого фрагмента данных; создание методов equals
, hashCode
и toString
; и создание конструктора, который принимает каждое поле.
Хотя IDE могут автоматически генерировать многие из этих классов, они не могут автоматически обновлять наши классы, когда мы добавляем новое поле . Например, если мы добавляем новое поле, нам нужно обновить наш метод equals
, чтобы включить это поле.
Во втором случае дополнительный код скрывает, что наш класс — это просто класс данных , который имеет два поля String ,
имя
и адрес
.
Лучшим подходом было бы явно объявить, что наш класс является классом данных.
3. Основы
Начиная с JDK 14, мы можем заменить наши повторяющиеся классы данных записями. Записи — это неизменяемые классы данных, для которых требуется только тип и имя полей.
Методы equals
, hashCode
и toString
, а также поля private,
final и
открытый
конструктор генерируются компилятором Java.
Чтобы создать запись человека
, мы будем использовать ключевое слово записи
:
public record Person (String name, String address) {}
3.1. Конструктор
С помощью записей для нас генерируется публичный конструктор с аргументом для каждого поля.
В случае нашей записи Person
эквивалентный конструктор:
public Person(String name, String address) {
this.name = name;
this.address = address;
}
Этот конструктор можно использовать так же, как класс, для создания экземпляров объектов из записи:
Person person = new Person("John Doe", "100 Linda Ln.");
3.2. Добытчики
Также мы бесплатно получаем публичные геттеры, имена которых совпадают с названием нашего поля.
В нашей записи Person
это означает геттер name()
и address() :
@Test
public void givenValidNameAndAddress_whenGetNameAndAddress_thenExpectedValuesReturned() {
String name = "John Doe";
String address = "100 Linda Ln.";
Person person = new Person(name, address);
assertEquals(name, person.name());
assertEquals(address, person.address());
}
3.3. равно
Кроме того, для нас генерируется метод equals .
Этот метод возвращает true
, если предоставленный объект имеет тот же тип и значения всех его полей совпадают:
@Test
public void givenSameNameAndAddress_whenEquals_thenPersonsEqual() {
String name = "John Doe";
String address = "100 Linda Ln.";
Person person1 = new Person(name, address);
Person person2 = new Person(name, address);
assertTrue(person1.equals(person2));
}
Если какие-либо поля различаются между двумя экземплярами Person , метод
equals
вернет false
.
3.4. хэш-код
Аналогично нашему методу equals
для нас также генерируется соответствующий метод hashCode .
Наш метод hashCode возвращает одно и то же значение для двух объектов
Person
, если все значения полей для обоих объектов совпадают (за исключением коллизий из-за парадокса дня рождения ) :
@Test
public void givenSameNameAndAddress_whenHashCode_thenPersonsEqual() {
String name = "John Doe";
String address = "100 Linda Ln.";
Person person1 = new Person(name, address);
Person person2 = new Person(name, address);
assertEquals(person1.hashCode(), person2.hashCode());
}
Значение hashCode
будет отличаться, если какое-либо из значений поля отличается.
3.5. нанизывать
Наконец, мы также получаем метод toString
, результатом которого является строка, содержащая имя записи, за которой следует имя каждого поля и соответствующее ему значение в квадратных скобках . ``
Таким образом, создание экземпляра Person
с именем «John Doe»
и адресом «100 Linda Ln.
” приводит к следующему результату toString
:
Person[name=John Doe, address=100 Linda Ln.]
4. Конструкторы
Хотя для нас создается публичный конструктор, мы все равно можем настроить реализацию нашего конструктора.
Эта настройка предназначена для проверки и должна быть максимально простой.
Например, мы можем убедиться, что имя
и адрес
, предоставленные нашей записи Person , не равны
нулю
, используя следующую реализацию конструктора:
public record Person(String name, String address) {
public Person {
Objects.requireNonNull(name);
Objects.requireNonNull(address);
}
}
Мы также можем создавать новые конструкторы с другими аргументами, указав другой список аргументов:
public record Person(String name, String address) {
public Person(String name) {
this(name, "Unknown");
}
}
Как и в случае с конструкторами классов, на поля можно ссылаться с помощью ключевого слова this
(например, this.name
и this.address
), а аргументы соответствуют именам полей (т . е. name
и address
).
Обратите внимание, что создание конструктора с теми же аргументами, что и сгенерированный общедоступный конструктор, допустимо, но для этого требуется, чтобы каждое поле было инициализировано вручную :
public record Person(String name, String address) {
public Person(String name, String address) {
this.name = name;
this.address = address;
}
}
Кроме того, объявление конструктора без аргументов и конструктора со списком аргументов, совпадающим со сгенерированным конструктором, приводит к ошибке компиляции .
Поэтому следующее не будет компилироваться:
public record Person(String name, String address) {
public Person {
Objects.requireNonNull(name);
Objects.requireNonNull(address);
}
public Person(String name, String address) {
this.name = name;
this.address = address;
}
}
5. Статические переменные и методы
Как и в случае с обычными классами Java, мы также можем включать в свои записи статические переменные и методы .
Мы объявляем статические переменные, используя тот же синтаксис, что и класс:
public record Person(String name, String address) {
public static String UNKNOWN_ADDRESS = "Unknown";
}
Точно так же мы объявляем статические методы, используя тот же синтаксис, что и класс:
public record Person(String name, String address) {
public static Person unnamed(String address) {
return new Person("Unnamed", address);
}
}
Затем мы можем ссылаться как на статические переменные, так и на статические методы, используя имя записи:
Person.UNKNOWN_ADDRESS
Person.unnamed("100 Linda Ln.");
6. Заключение
В этой статье мы рассмотрели ключевое слово записи
, введенное в Java 14, включая основные концепции и тонкости.
Используя записи с их методами, сгенерированными компилятором, мы можем сократить шаблонный код и повысить надежность наших неизменяемых классов.
Код и примеры для этой статьи можно найти на GitHub .