Перейти к основному содержимому

Ключевое слово записи Java 14

· 7 мин. чтения

1. Введение

Передача неизменяемых данных между объектами — одна из самых распространенных, но рутинных задач во многих приложениях Java.

До Java 14 это требовало создания класса с шаблонными полями и методами, которые были подвержены тривиальным ошибкам и путаным намерениям.

С выпуском Java 14 мы теперь можем использовать записи для решения этих проблем.

В этом руководстве мы рассмотрим основы записей , включая их назначение, сгенерированные методы и методы настройки .

2. Цель

Обычно мы пишем классы, чтобы просто хранить данные, такие как результаты базы данных, результаты запросов или информацию из службы.

Во многих случаях эти данные являются неизменяемыми , поскольку неизменность обеспечивает достоверность данных без синхронизации .

Для этого мы создаем классы данных со следующим:

  1. private , конечное поле для каждого фрагмента данных
  2. геттер для каждого поля
  3. публичный конструктор с соответствующим аргументом для каждого поля
  4. equals , который возвращает true для объектов одного класса, когда все поля совпадают
  5. `метод hashCode , который возвращает одно и то же значение, когда все поля совпадают`
  6. метод 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
}

Хотя это достигает нашей цели, есть две проблемы:

  1. Там много стандартного кода
  2. Мы затемняем цель нашего класса: представлять человека с именем и адресом

В первом случае нам приходится повторять один и тот же утомительный процесс для каждого класса данных, монотонно создавая новое поле для каждого фрагмента данных; создание методов 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 .