Поиск по сайту:

Класс записей Java 14


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

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

Рекомендуемая литература: возможности Java 14

Зачем нам нужны записи Java?

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

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

Эта многословность — одна из причин повышенного интереса к Project Lombok.

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

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

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

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

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

  1. JDK 14 установлен. Если вы используете IDE, она также должна поддерживать Java 14. И Eclipse, и IntelliJ уже поддерживают Java 14, так что у нас все хорошо.
  2. Включить функцию предварительного просмотра. По умолчанию функции предварительного просмотра отключены. Вы можете включить его в Eclipse в настройках компилятора Project Java.

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

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();
}
# 

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

# javap -v EmpRecord 

Важные моменты о классах записи

  1. Класс Record является окончательным, поэтому мы не можем его расширять.
  2. Класс Record неявно расширяет класс java.lang.Record.
  3. Все поля, указанные в объявлении записи, являются окончательными.
  4. Поля записи являются \поверхностными неизменяемыми и зависят от типа. Например, мы можем изменить поле адресов, обратившись к нему и затем внеся в него обновления.
  5. Создается один конструктор со всеми полями, указанными в определении записи.
  6. Класс Record автоматически предоставляет методы доступа к полям. Имя метода такое же, как и имя поля, в отличие от универсальных и обычных методов получения.
  7. Класс Record также предоставляет реализации hashCode(), equals() и toString().

Использование записей в программе Java

Давайте рассмотрим простой пример использования нашего класса EmpRecord.

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);

		// toString()
		System.out.println(empRecord1);
		
		// accessing fields
		System.out.println("Name: "+empRecord1.name()); 
		System.out.println("ID: "+empRecord1.id());
		
		// equals()
		System.out.println(empRecord1.equals(empRecord2));
		
		// hashCode()
		System.out.println(empRecord1 == empRecord2);		
	}
}

Выход:

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

Объект 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, как в следующем коде:

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)

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

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

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;
	}
}

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

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

Заключение

Записи Java — долгожданное дополнение к основным функциям программирования. Вы можете думать об этом как о «именованном кортеже». Он предназначен для создания объекта-носителя данных с компактной структурой, избегая всего шаблонного кода.