Java 14 Records类详解:简化数据类的新特性

Java 14推出了一种称为”记录(Records)”的新方式来创建类。在本教程中,我们将学习:

  • 为什么我们需要Java记录
  • 如何创建记录并使用它
  • 重写和扩展记录类

推荐阅读:Java 14 特性

为什么我们需要Java记录?

Java一直以来最常见的抱怨之一就是其冗长性。如果你需要创建一个简单的POJO(Plain Old Java Object)类,就需要以下样板代码:

  • 私有字段
  • Getter和Setter方法
  • 构造函数
  • hashCode()、equals()和toString()方法

这种啰嗦是导致Kotlin和Project Lombok备受关注的原因之一。

实际上,每次都要编写这些通用方法的纯粹沮丧,导致了在Java IDE(如Eclipse和IntelliJ IDEA)中创建它们的快捷方式。

这是显示Eclipse IDE生成类的仪式方法选项的屏幕截图。

Eclipse Shortcuts Generate Ceremonial Methods

Java记录旨在通过提供紧凑的结构来创建POJO类,以消除这种冗长。

如何创建Java记录

Java记录是一个预览功能,它是在JEP 359下开发的。因此,要在你的Java项目中创建记录,你需要两个东西:

  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的预览功能。

假设我想创建一个员工模型类。它的代码大致如下。

package com.scdev.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多行的自动生成代码。现在让我们看看如何创建一个员工记录类,它本质上提供了相同的功能。

package com.scdev.java14;

import java.util.Map;

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

哇,这真的无法再短了。我已经爱上了Records类。

现在,让我们使用javap命令来了解在编译某个记录时发生的背后情况。

# javac --enable-preview -source 14 EmpRecord.java
注意:EmpRecord.java 使用了预览语言功能。
注意:请使用 -Xlint:preview 重新编译以获取详细信息。

# javap EmpRecord      
编译自 "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 类详细信息

如果你想要更多的内部细节,请使用javap命令并加上-v选项。

# javap -v EmpRecord

记录类的重要要点

记录类的关键要点:

  1. 一个Record类是final的,所以我们不能扩展它。
  2. Record类隐式地扩展了java.lang.Record类。
  3. 记录声明中指定的所有字段都是final的。
  4. 记录字段是”浅”不可变的,并且依赖于类型。例如,我们可以通过访问并更新地址字段来更改它。
  5. 创建一个单一的构造函数,其中包含记录定义中指定的所有字段。
  6. Record类自动为字段提供访问方法。方法名与字段名相同,不像通用和常规的getter方法。
  7. Record类还提供了hashCode()、equals()和toString()的实现。

在Java程序中使用记录

让我们看一个使用我们的EmpRecord类的简单例子。

package com.scdev.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);
        
        // 访问字段
        System.out.println("姓名: "+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对象的工作方式与任何模型类、数据对象等相同。

扩展记录构造函数

有时候,我们希望在构造函数中进行一些验证或记录。例如,雇员ID和薪水不应该为负值。默认的构造函数没有这个验证。我们可以在记录类中创建一个简洁的构造函数。这个构造函数的代码将放置在自动生成的构造函数的开头。

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.scdev.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类来说,拥有一个方法是必须的,那么请仔细思考一下,你真的需要一个Record类吗?

结论

Java Records是核心编程特性的一个受欢迎补充。你可以把它看作是一个”命名元组”。它的目的是创建一个具有紧凑结构的数据载体对象,避免所有样板代码。

bannerAds