JavaRush /Java 博客 /Random-ZH /喝咖啡休息#128。Java 记录指南

喝咖啡休息#128。Java 记录指南

已在 Random-ZH 群组中发布
来源:abhinavpandey.dev 在本教程中,我们将介绍在 Java 中使用 Records 的基础知识。Java 14 中引入了记录,作为一种在利用不可变对象的同时删除创建 Value 对象的样板代码的方法。 喝咖啡休息#128。 Java 记录指南 - 1

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. 值对象的不变性

值对象必须是不可变的。这意味着我们必须限制更改对象字段的方式。出于以下原因,建议这样做:
  • 避免意外更改字段值的风险。
  • 确保平等的物体终生保持不变。
由于Contact类已经是不可变的,我们现在:
  1. 使这些领域成为私有的最终的
  2. 只为每个字段提供一个 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. 默认行为

我们已将代码减少到一行。让我们列出它包含的内容:
  1. 默认情况下,姓名电子邮件字段是私人且最终的。

  2. 该代码定义了一个将字段作为参数的“规范构造函数”。

  3. 可以通过类似于 getter 的方法访问字段 - name()email()。字段没有设置器,因此对象中的数据变得不可变。

  4. 实现了toString方法来打印字段,就像我们为Contact类所做的那样。

  5. 实现了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 +
                ']';
    }
}
同样,我们可以重写equalshashCode方法。

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;
    }
}
请注意,静态字段中没有隐式限制。如有必要,它们可能会公开,但不是最终版本。

结论

记录是定义数据类的好方法。它们比 JavaBeans/POJO 方法更方便、更强大。因为它们易于实现,所以它们应该比其他创建值对象的方法更受青睐。
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION