来源:abhinavpandey.dev 在本教程中,我们将介绍在 Java 中使用 Records 的基础知识。Java 14 中引入了记录,作为一种在利用不可变对象的同时删除创建 Value 对象的样板代码的方法。
1. 基本概念
在我们深入研究条目本身之前,让我们先看看它们解决的问题。为此,我们必须记住 Java 14 之前如何创建值对象。1.1. 值对象
值对象是 Java 应用程序的一个组成部分。它们存储需要在应用程序层之间传输的数据。值对象包含字段、构造函数以及用于访问这些字段的方法。下面是一个值对象的示例:public class Contact {
private final String name;
private final String email;
public Contact(String name, String email) {
this.name = name;
this.email = email;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
}
1.2. 值对象之间的相等性
值对象还可以提供一种比较它们是否相等的方法。默认情况下,Java通过比较对象的内存地址来比较对象的相等性。然而,在某些情况下,包含相同数据的对象可能被认为是相等的。为了实现这一点,我们可以重写equals和.hashCode方法。让我们为Contact类实现它们:public class Contact {
// ...
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Contact contact = (Contact) o;
return Object.equals(email, contact.email) &&
Objects.equals(name, contact.name);
}
@Override
public int hashCode() {
return Objects.hash(name, email);
}
}
1.3. 值对象的不变性
值对象必须是不可变的。这意味着我们必须限制更改对象字段的方式。出于以下原因,建议这样做:- 避免意外更改字段值的风险。
- 确保平等的物体终生保持不变。
- 使这些领域成为私有的和最终的。
- 只为每个字段提供一个 getter(没有setters)。
1.4. 注册值对象
很多时候我们需要注册对象中包含的值。这是通过提供toString方法来完成的。每当注册或打印对象时,都会调用toString方法。这里最简单的方法是打印每个字段的值。这是一个例子:public class Contact {
// ...
@Override
public String toString() {
return "Contact[" +
"name='" + name + '\'' +
", email=" + email +
']';
}
}
2. 使用记录减少模板
由于大多数值对象具有相同的需求和功能,因此简化创建它们的过程会很好。让我们看看录音如何帮助实现这一目标。2.1. 将 Person 类转换为 Record
让我们创建一个与上面定义的Contact类具有相同功能的Contact类条目。public record Contact(String name, String email) {}
record 关键字用于创建Record类。调用者可以按照与类相同的方式处理记录。例如,要创建一个新的条目实例,我们可以使用new关键字。
Contact contact = new Contact("John Doe", "johnrocks@gmail.com");
2.2. 默认行为
我们已将代码减少到一行。让我们列出它包含的内容:-
默认情况下,姓名和电子邮件字段是私人且最终的。
-
该代码定义了一个将字段作为参数的“规范构造函数”。
-
可以通过类似于 getter 的方法访问字段 - name()和email()。字段没有设置器,因此对象中的数据变得不可变。
-
实现了toString方法来打印字段,就像我们为Contact类所做的那样。
-
实现了equals和.hashCode方法。它们包括所有字段,就像Contact类一样。
2.3 规范构造函数
默认构造函数将所有字段作为输入参数并将它们设置为字段。例如,默认的规范构造函数如下所示:public Contact(String name, String email) {
this.name = name;
this.email = email;
}
如果我们在记录类中定义具有相同签名的构造函数,则将使用它而不是规范构造函数。
3. 处理记录
我们可以通过多种方式改变条目的行为。让我们看一些用例以及如何实现它们。3.1. 覆盖默认实现
任何默认实现都可以通过覆盖它来更改。例如,如果我们想更改toString方法的行为,那么我们可以在大括号{}之间覆盖它。public record Contact(String name, String email) {
@Override
public String toString() {
return "Contact[" +
"name is '" + name + '\'' +
", email is" + email +
']';
}
}
同样,我们可以重写equals和hashCode方法。
3.2. 紧凑型施工套件
有时我们希望构造函数做的不仅仅是初始化字段。为此,我们可以将必要的操作添加到紧凑构造函数中的条目中。之所以称为紧凑,是因为它不需要定义字段初始化或参数列表。public record Contact(String name, String email) {
public Contact {
if(!email.contains("@")) {
throw new IllegalArgumentException("Invalid email");
}
}
}
请注意,没有参数列表,并且在执行检查之前,名称和电子邮件的初始化在后台进行。
3.3. 添加构造函数
您可以向一条记录添加多个构造函数。让我们看几个示例和限制。首先,让我们添加新的有效构造函数:public record Contact(String name, String email) {
public Contact(String email) {
this("John Doe", email);
}
// replaces the default constructor
public Contact(String name, String email) {
this.name = name;
this.email = email;
}
}
在第一种情况下,使用this关键字 访问默认构造函数。第二个构造函数会覆盖默认构造函数,因为它具有相同的参数列表。在这种情况下,条目本身不会创建默认构造函数。对构造函数有一些限制。
1. 应始终从任何其他构造函数调用默认构造函数。
例如,下面的代码将无法编译:public record Contact(String name, String email) {
public Contact(String name) {
this.name = "John Doe";
this.email = null;
}
}
该规则确保字段始终被初始化。还保证紧凑构造函数中定义的操作始终被执行。
2. 如果定义了紧凑构造函数,则无法覆盖默认构造函数。
定义紧凑构造函数时,会自动创建一个带有初始化和紧凑构造函数逻辑的默认构造函数。在这种情况下,编译器将不允许我们定义与默认构造函数具有相同参数的构造函数。例如,在这段代码中,编译不会发生:public record Contact(String name, String email) {
public Contact {
if(!email.contains("@")) {
throw new IllegalArgumentException("Invalid email");
}
}
public Contact(String name, String email) {
this.name = name;
this.email = email;
}
}
3.4. 实现接口
与任何类一样,我们可以在记录中实现接口。public record Contact(String name, String email) implements Comparable<Contact> {
@Override
public int compareTo(Contact o) {
return name.compareTo(o.name);
}
}
重要的提示。为了确保完全的不变性,记录不能被继承。参赛作品是最终作品,不能扩大。他们也不能扩展其他课程。
3.5. 添加方法
除了重写方法和接口实现的构造函数之外,我们还可以添加任何我们想要的方法。例如:public record Contact(String name, String email) {
String printName() {
return "My name is:" + this.name;
}
}
我们还可以添加静态方法。例如,如果我们想要一个静态方法返回一个正则表达式,我们可以根据该正则表达式检查电子邮件,那么我们可以如下所示定义它:
public record Contact(String name, String email) {
static Pattern emailRegex() {
return Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
}
}
3.6. 添加字段
我们无法将实例字段添加到记录中。但是,我们可以添加静态字段。public record Contact(String name, String email) {
private static final Pattern EMAIL_REGEX_PATTERN = Pattern
.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
static Pattern emailRegex() {
return EMAIL_REGEX_PATTERN;
}
}
请注意,静态字段中没有隐式限制。如有必要,它们可能会公开,但不是最终版本。
GO TO FULL VERSION