我们在开发时会借助Lombok快速填充POJO类的模板方法,比如:getter、setter、equals、hashCode等,引入这个组件的原因是方便,一个注解就可以代理一堆的模板方法。
从Java16开始,我们可以借助Record类型实现相同的功能了,接下来,我们详细看下这个类型。
Record是Java中的一种特殊类,旨在为程序员提供一种高效且简便的方式实现POJO类。Record类型通过关键字record实现。
一、Java中的Record类是什么?
在Java项目开发中,作为开发者,我们常常编写服务类、安全类或其他基础类,这些类本质上是功能性的。同样,程序员也经常编写仅用于承载数据的类,即POJO类。
例如,假设客户端向服务器请求某个人的“id”和“name”等数据,服务器会以相应的数据进行响应。
由于Java中一切皆为对象,所以必然存在某个类来承载这些数据,服务器会将该类的对象返回给客户端,该对象的唯一目的就是将数据从服务器传递到客户端。
然而,编写这样一个数据类,即便它可能只是一个简单的POJO,也会包含大量的模板代码,比如私有字段、构造函数、getter和setter方法、hashCode()、equals()和toString()方法。
由于Java语言的冗长特性,一个简单的承载类会因大量不必要的代码而变得臃肿。
这些弊端促使了一种特殊类型的类Record的诞生。该类能够聚合(或持有)一组值,无需编写自动生成样板代码,可以说是一种高效的数据对象构建方式。
虽然,即使没有Record类型,我们也能写好代码,比如借助IDEA的模板生成,比如借助Lombok的注解,但是,原生支持的能力,在便利性和效率方面,可以给代码注入新的灵魂。
我们看下以前的写法:
import java.util.Objects;
public class Person {
private int id;
private String name;
public Person() {}
public Person(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass()!= o.getClass()) return false;
Person person = (Person) o;
return getId() == person.getId() && getName().equals(person.getName());
}
@Override
public int hashCode() {
return Objects.hash(getId(), getName());
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
是不是看着都不想写了。
二、如何在Java中创建Record类?
Java中的Record类由关键字record支持,语法为:
record recordName(list-of-components) {
//可选语句
}
以record开头,后面跟着类名和参数列表。此时,我们可以把前面的Person改写为:
record Person(int id, String name){}
一下子就清爽了。
使用起来和原来没有太多差异:
Person p1 = new Person(1, "Peter Parker");
在编译过程中,编译器会自动提供存储数据所需的元素、构造函数、访问数据的getter方法、toString()、equals()和hashCode()方法。因此,虽然没有写下面这些方法,也能够正常使用。
Person p1 = new Person(1, "Peter Parker");
Person p2 = new Person(2, "Spiderman");
System.out.println(p1.toString());
System.out.println(p1.equals(p2));
System.out.println(p1.name());
关于上述代码示例,有以下几个要点需要注意:
- Record类提供的规范构造函数包含与组件列表中传递的相同参数,且顺序一致。传递的值会自动赋给记录字段。
- Record类通过“new”关键字进行实例化,就像在Java中创建其他任何对象一样。
- Record类中的数据保存在私有final字段中,并且只有getter方法。因此,记录中的数据是不可变的。
- Record类不能继承其他类。是因为已经隐式继承java.lang.Record。并重写了Object类的equals()、hashCode()和toString()方法。
- Record类声明都是final的,所以它们不能被扩展。
- Record类可以实现一个或多个接口。
- 除了声明的参数外,任何其他字段必须声明为静态的。
三、Java中的规范构造函数
在Record中,如果需要定义规范构造函数,需要按照预设格式定义。我们有两种方式来声明我们自己的实现。
方式一、常用的规范构造函数:
record Invoice(String id, float amount) {
static String prefix = String.valueOf(Calendar.getInstance().get(Calendar.YEAR))
+ String.valueOf(Calendar.getInstance().get(Calendar.MONTH) + 1);
public Invoice(String id, float amount) {
this.id = prefix + id.trim();
this.amount = amount;
}
}
方式二、紧凑构造函数。在紧凑构造函数中,签名声明是隐式的。我们只需提供Record类名作为构造函数,不带任何参数。这种类型的构造函数具有所有参数的隐式声明,并且会自动将传递给记录组件的值赋给相应参数。此外,在紧凑构造函数中,不使用this关键字。
record Invoice(String id, float amount) {
static String prefix = String.valueOf(Calendar.getInstance().get(Calendar.YEAR))
+ String.valueOf(Calendar.getInstance().get(Calendar.MONTH) + 1);
public Invoice {
id = prefix + id.trim();
amount = amount;
}
}
四、Java中的非规范构造函数
除了规范构造函数,我们还可以定义非规范构造函数。比如:当我们只想用默认初始化一个字段,此时,我们可以编写一个非规范构造函数。
非规范构造函数的关键要求是,构造函数必须使用this关键字调用记录中的另一个规范构造函数。
以下是一个简单示例:
record Invoice(String id, float amount) {
static String prefix = String.valueOf(Calendar.getInstance().get(Calendar.YEAR))
+ String.valueOf(Calendar.getInstance().get(Calendar.MONTH) + 1);
public Invoice {
id = prefix + id.trim();
amount = amount;
}
public Invoice(String id) {
this(id, 0.0f);
}
}
非规范构造函数就像是预设默认值的一种实现。
五、Java Record类代码示例
最后,我们来个示例代码,展示了如何在Java中使用Record类、规范构造函数和非规范构造函数:
record Invoice(String id, float amount) {
static String prefix = String.valueOf(Calendar.getInstance().get(Calendar.YEAR))
+ String.valueOf(Calendar.getInstance().get(Calendar.MONTH) + 1);
public Invoice {
id = prefix + id.trim();
if (amount < 0)
throw new IllegalArgumentException("-ve values not allowed");
amount = amount;
}
public Invoice(String id) {
this(id, 0.0f);
}
}
public class App {
public static void main(String[] args) {
float[] amt = {400.00f, 600.00f, 300.00f, 700.00f, 600.00f};
Invoice[] invoice = new Invoice[5];
for (int i = 0; i < invoice.length; i++)
invoice[i] = new Invoice(String.valueOf(i + 1), amt[i]);
for (int i = 0; i < invoice.length; i++)
System.out.println(invoice[i].toString());
}
}
文末总结
Record类和record关键字的引入,极大地提升了定义类的便捷性,打破了Java语言规范中POJO类冗长的声明。我们可以不用他,但是要知道其用法。就像是Java8的stream一样,Record类也有很大魅力。