Класс Records в Java 14

Java 14 представил новый способ создания классов, называемый Records. В этом учебнике мы узнаем:

  • Зачем нам нужны Java Records
  • Как создавать Records и использовать их
  • Переопределение и расширение классов Records

Рекомендуемая литература: Особенности Java 14

Зачем нам нужны Java Records?

Одной из общих жалоб на Java была ее многословность. Если вам нужно создать простой класс POJO, требуется следующий шаблонный код.

  • Приватные поля
  • Методы Getter и Setter
  • Конструкторы
  • Методы hashCode(), equals() и toString().

Именно этот излишний объем кода является одной из причин высокого интереса к Kotlin и Project Lombok.

Фактически, чрезмерная надоедливость при написании этих общих методов каждый раз привела к использованию быстрых способов создания их в средах разработки Java, таких как Eclipse и IntelliJ IDEA.

Вот скриншот, показывающий опцию Eclipse IDE для создания церемониальных методов для класса.

Eclipse Shortcuts to Generate Ceremonial Methods

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

Как создать записи Java

Записи Java – это функция предварительного просмотра, которая разрабатывается в рамках JEP 359. Поэтому вам нужно две вещи, чтобы создать записи в своих проектах Java.

  1. Установлен JDK 14. Если вы используете среду разработки, она также должна поддерживать Java 14. Как Eclipse, так и IntelliJ уже поддерживают Java 14, поэтому здесь мы в порядке.
  2. Включите функцию предварительного просмотра: По умолчанию функции предварительного просмотра отключены. Вы можете включить их в Eclipse в настройках компилятора Java проекта.
Java 14 Enable Preview Feature In Eclipse

Вы можете включить функции предварительного просмотра Java 14 в командной строке, используя опцию --enable-preview -source 14.

Допустим, я хочу создать класс модели Employee. Он будет выглядеть примерно так:

package com.journaldev.java14;

import java.util.Map;

public class Employee {

	private int id;
	private String name;
	private long salary;
	private Map<String, String> addresses;

	public Employee(int id, String name, long salary, Map<String, String> addresses) {
		super();
		this.id = id;
		this.name = name;
		this.salary = salary;
		this.addresses = addresses;
	}

	public int getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	public long getSalary() {
		return salary;
	}

	public Map<String, String> getAddresses() {
		return addresses;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((addresses == null) ? 0 : addresses.hashCode());
		result = prime * result + id;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		result = prime * result + (int) (salary ^ (salary >>> 32));
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Employee other = (Employee) obj;
		if (addresses == null) {
			if (other.addresses != null)
				return false;
		} else if (!addresses.equals(other.addresses))
			return false;
		if (id != other.id)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		if (salary != other.salary)
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + ", addresses=" + addresses + "]";
	}

}

Уф, это более 70 строк автоматически сгенерированного кода. Теперь давайте посмотрим, как создать класс записи Employee, который в основном предоставляет те же функции.

package com.journaldev.java14;

import java.util.Map;

public record EmpRecord(int id, String name, long salary, Map<String, String> addresses) {
}

Вау, это уже нельзя сделать короче. Я уже в восторге от классов записей.

Теперь давайте используем команду javap, чтобы понять, что происходит за кулисами при компиляции записи.

# javac --enable-preview -source 14 EmpRecord.java
Note: EmpRecord.java uses preview language features.
Note: Recompile with -Xlint:preview for details.

# javap EmpRecord      
Compiled from "EmpRecord.java"
public final class EmpRecord extends java.lang.Record {
  public EmpRecord(int, java.lang.String, long, java.util.Map<java.lang.String, java.lang.String>);
  public java.lang.String toString();
  public final int hashCode();
  public final boolean equals(java.lang.Object);
  public int id();
  public java.lang.String name();
  public long salary();
  public java.util.Map<java.lang.String, java.lang.String> addresses();
}
# 
Java Record Class Details

# javac enablepreview source 14 EmpRecord.java

# javap EmpRecord

Если вам нужны более подробные внутренние сведения, выполните команду javap с опцией -v.

  1. A Record class is final, so we can’t extend it.
  2. # javap -v EmpRecord
  3. Важные моменты о классах записей
  4. Классы Record неявно расширяют класс java.lang.Record.
  5. A single constructor is created with all the fields specified in the record definition.
  6. Все поля, указанные в объявлении записи, являются финальными.
  7. Поля записи “поверхностно” неизменны и зависят от типа. Например, мы можем изменить поле addresses, обращаясь к нему, а затем внося изменения.

Класс Record автоматически предоставляет методы доступа к полям. Имя метода такое же, как и имя поля, не такое как у обычных геттеров.

Класс Record также предоставляет реализации методов hashCode(), equals() и toString().

package com.journaldev.java14;

public class RecordTest {

	public static void main(String[] args) {
		
		EmpRecord empRecord1 = new EmpRecord(10, "Pankaj", 10000, null);
		EmpRecord empRecord2 = new EmpRecord(10, "Pankaj", 10000, null);

		Использование записей в программе на Java
		System.out.println(empRecord1);
		
		Давайте рассмотрим простой пример использования нашего класса EmpRecord.
		System.out.println("Name: "+empRecord1.name()); 
		System.out.println("ID: "+empRecord1.id());
		
		// toString()
		System.out.println(empRecord1.equals(empRecord2));
		
		// доступ к полям
		System.out.println(empRecord1 == empRecord2);		
	}
}

// equals()

EmpRecord[id=10, name=Pankaj, salary=10000, addresses=null]
Name: Pankaj
ID: 10
true
false

// hashCode()

Вывод:

Объект Record работает так же, как любой модельный класс, объект данных и т. д.

public record EmpRecord(int id, String name, long salary, Map<String, String> addresses) {
	
	public EmpRecord {
		if (id < 0)
			throw new IllegalArgumentException("employee id can't be negative");

		if (salary < 0)
			throw new IllegalArgumentException("employee salary can't be negative");
	}

}

Расширение конструктора записей

EmpRecord empRecord1 = new EmpRecord(-10, "Pankaj", 10000, null);

Иногда нам нужно иметь некоторые проверки или журналирование в нашем конструкторе. Например, идентификатор сотрудника и зарплата не должны быть отрицательными. В конструкторе по умолчанию эта проверка отсутствует. Мы можем создать компактный конструктор в классе записи. Код этого конструктора будет размещен в начале автоматически сгенерированного конструктора.

Exception in thread "main" java.lang.IllegalArgumentException: employee id can't be negative
	at com.journaldev.java14.EmpRecord.<init>(EmpRecord.java:9)

Если мы создадим EmpRecord, как показано в следующем коде:

Мы получим исключение во время выполнения:

public record EmpRecord(int id, String name, long salary, Map<String, String> addresses) {

	public int getAddressCount() {
		if (this.addresses != null)
			return this.addresses().size();
		else
			return 0;
	}
}

Могут ли у записей быть методы?

Да, мы можем создавать методы в записях.

Однако, записи предназначены для того, чтобы быть носителями данных. Мы должны избегать наличия вспомогательных методов в классе записей. Например, вышеупомянутый метод может быть создан в классе утилит.

Если вы считаете, что наличие метода необходимо для вашего класса записей, обдумайте, действительно ли вам нужен класс записей?

Source:
https://www.digitalocean.com/community/tutorials/java-records-class