Java 14 記錄類別

Java 14 推出了一種稱為 Records 的新建類方式。在本教程中,我們將學到:

  • 為什麼我們需要 Java Records
  • 如何創建 Records 並使用它
  • 覆蓋和擴展 Records 類

推薦閱讀Java 14 功能

為什麼我們需要 Java Records?

Java 的一個常見抱怨是其冗長性。如果你要創建一個簡單的 POJO 類,就需要以下樣板代碼。

  • 私有字段
  • Getter 和 Setter 方法
  • 構造函數
  • hashCode()、equals() 和 toString() 方法。

這種冗長性正是導致人們對 KotlinProject Lombok 表現出濃厚興趣的原因之一。

事實上,每次都要編寫這些通用方法的純粹沮喪感促使在 Java IDE(如 Eclipse 和 IntelliJ IDEA)中創建它們的快捷方式。

這是顯示 Eclipse IDE 選項以生成類的禮儀方法的螢幕截圖。

Eclipse Shortcuts to Generate Ceremonial Methods

Java Records 旨在通過提供一種簡潔的結構來創建 POJO 類,從而消除這種冗長性。

如何创建Java Records

Java Records是一个预览功能,是在JEP 359下开发的。因此,在Java项目中创建Records,您需要两样东西。

  1. 已安装JDK 14。如果您使用的是IDE,那么它必须支持Java 14。Eclipse和IntelliJ已经支持Java 14,所以我们在这方面很好。
  2. 启用预览功能:默认情况下,预览功能是禁用的。您可以在Eclipse的项目Java编译器设置中启用它。
Java 14 Enable Preview Feature In Eclipse

您可以使用--enable-preview -source 14选项在命令行中启用Java 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) {
}

哇,这不能再简短了。我已经喜欢上Record类了。

现在,让我们使用javap命令来弄清楚Record编译时发生了什么。

# 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

如果您想要更多內部詳細信息,請使用 -v 選項運行 javap 命令。

  1. A Record class is final, so we can’t extend it.
  2. # javap -v EmpRecord
  3. 關於記錄類的重要要點
  4. 記錄類隱式擴展 java.lang.Record 類。
  5. A single constructor is created with all the fields specified in the record definition.
  6. 記錄聲明中指定的所有字段都是 final 的。
  7. 記錄字段是“淺層”不可變的,取決於類型。例如,我們可以通過訪問地址字段,然後對其進行更新來更改地址字段。

記錄類自動為字段提供存取器方法。方法名與字段名相同,而不像通用的和傳統的 getter 方法。

記錄類還提供了 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()

輸出:

記錄物件的運作方式與任何模型類別、資料物件等相同。

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

有時候,我們希望在我們的建構子中進行一些驗證或記錄。例如,員工ID和薪水不應該是負數。預設建構子不會進行這種驗證。我們可以在記錄類別中建立一個緊湊的建構子。這個建構子的程式碼將被放置在自動產生的建構子的開頭。

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