Java 14 Record Keyword – Java 14 记录关键字

最后修改: 2020年 5月 14日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

Passing immutable data between objects is one of the most common, but mundane tasks in many Java applications.

在对象之间传递不可变的数据是许多Java应用程序中最常见但又最平凡的任务之一。

Prior to Java 14, this required the creation of a class with boilerplate fields and methods, which were susceptible to trivial mistakes and muddled intentions.

在Java 14之前,这需要创建一个带有模板字段和方法的类,这很容易出现琐碎的错误和糊涂的意图。

With the release of Java 14, we can now use records to remedy these problems.

随着Java 14的发布,我们现在可以使用记录来补救这些问题。

In this tutorial, we’ll look at the fundamentals of records, including their purpose, generated methods, and customization techniques.

在本教程中,我们将看看记录的基本原理包括它们的目的,生成的方法,以及自定义技术

2. Purpose

2.宗旨

Commonly, we write classes to simply hold data, such as database results, query results, or information from a service.

通常,我们写类只是为了保存数据,如数据库结果、查询结果或来自服务的信息。

In many cases, this data is immutable, since immutability ensures the validity of the data without synchronization.

在许多情况下,这些数据是不可变的,因为不可变性确保了数据的有效性,而无需同步

To accomplish this, we create data classes with the following:

为了实现这一目标,我们用以下方式创建数据类。

  1. private, final field for each piece of data
  2. getter for each field
  3. public constructor with a corresponding argument for each field
  4. equals method that returns true for objects of the same class when all fields match
  5. hashCode method that returns the same value when all fields match
  6. toString method that includes the name of the class and the name of each field and its corresponding value

For example, we can create a simple Person data class with a name and address:

例如,我们可以创建一个简单的Person数据类,有一个名字和地址。

public class Person {

    private final String name;
    private final String address;

    public Person(String name, String address) {
        this.name = name;
        this.address = address;
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, address);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        } else if (!(obj instanceof Person)) {
            return false;
        } else {
            Person other = (Person) obj;
            return Objects.equals(name, other.name)
              && Objects.equals(address, other.address);
        }
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", address=" + address + "]";
    }

    // standard getters
}

While this accomplishes our goal, there are two problems with it:

虽然这完成了我们的目标,但有两个问题。

  1. There’s a lot of boilerplate code
  2. We obscure the purpose of our class: to represent a person with a name and address

In the first case, we have to repeat the same tedious process for each data class, monotonously creating a new field for each piece of data; creating equalshashCode, and toString methods; and creating a constructor that accepts each field.

在第一种情况下,我们必须为每个数据类重复同样的繁琐过程,单调地为每个数据创建一个新字段;创建equalshashCodetoString方法;并创建一个接受每个字段的构造函数。

While IDEs can automatically generate many of these classes, they fail to automatically update our classes when we add a new field. For example, if we add a new field, we have to update our equals method to incorporate this field.

虽然IDE可以自动生成许多这样的类,但当我们添加一个新字段时,它们不能自动更新我们的类。例如,如果我们添加了一个新的字段,我们必须更新我们的equals方法以纳入这个字段。

In the second case, the extra code obscures that our class is simply a data class that has two String fields, name and address.

在第二种情况下,额外的代码掩盖了我们的类只是一个数据类,它有两个String字段:nameaddress

A better approach would be to explicitly declare that our class is a data class.

一个更好的方法是明确声明我们的类是一个数据类。

3. The Basics

3.基础知识

As of JDK 14, we can replace our repetitious data classes with records. Records are immutable data classes that require only the type and name of fields.

从 JDK 14 开始,我们可以用记录代替我们的重复数据类。记录是不可变的数据类,只需要字段的类型和名称。

The equalshashCode, and toString methods, as well as the private, final fields and public constructor, are generated by the Java compiler.

equalshashCodetoString方法,以及private, final字段和public构造函数,都是由Java编译器生成的。

To create a Person record, we’ll use the record keyword:

要创建一个Person记录,我们将使用record关键字。

public record Person (String name, String address) {}

3.1. Constructor

3.1. 建筑商

Using records, a public constructor, with an argument for each field, is generated for us.

使用记录,为我们生成了一个公共构造函数,每个字段都有一个参数,

In the case of our Person record, the equivalent constructor is:

在我们的Person记录的情况下,等同的构造函数是。

public Person(String name, String address) {
    this.name = name;
    this.address = address;
}

This constructor can be used in the same way as a class to instantiate objects from the record:

这个构造函数可以用与类相同的方式,从记录中实例化对象。

Person person = new Person("John Doe", "100 Linda Ln.");

3.2. Getters

3.2 获取器

We also receive public getters methods, whose names match the name of our field, for free.

我们还可以免费获得公共的getters方法,其名称与我们的字段名称一致。

In our Person record, this means a name() and address() getter:

在我们的Person记录中,这意味着一个name()address()获取器。

@Test
public void givenValidNameAndAddress_whenGetNameAndAddress_thenExpectedValuesReturned() {
    String name = "John Doe";
    String address = "100 Linda Ln.";

    Person person = new Person(name, address);

    assertEquals(name, person.name());
    assertEquals(address, person.address());
}

3.3. equals

3.3.等价物

Additionally, an equals method is generated for us.

此外,还为我们生成了一个equals方法。

This method returns true if the supplied object is of the same type and the values of all of its fields match:

如果提供的对象类型相同,并且其所有字段的值匹配,则该方法返回true

@Test
public void givenSameNameAndAddress_whenEquals_thenPersonsEqual() {
    String name = "John Doe";
    String address = "100 Linda Ln.";

    Person person1 = new Person(name, address);
    Person person2 = new Person(name, address);

    assertTrue(person1.equals(person2));
}

If any of the fields differ between two Person instances, the equals method will return false.

如果两个Person实例之间的任何字段不同,equals方法将返回false

3.4. hashCode

3.4hashCode

Similar to our equals method, a corresponding hashCode method is also generated for us.

与我们的equals方法类似,相应的hashCode方法也会为我们生成。

Our hashCode method returns the same value for two Person objects if all of the field values for both objects match (barring collisions due to the birthday paradox):

我们的hashCode方法为两个Person对象返回相同的值,如果两个对象的所有字段值都匹配的话(排除由于生日悖论造成的碰撞)

@Test
public void givenSameNameAndAddress_whenHashCode_thenPersonsEqual() {
    String name = "John Doe";
    String address = "100 Linda Ln.";

    Person person1 = new Person(name, address);
    Person person2 = new Person(name, address);

    assertEquals(person1.hashCode(), person2.hashCode());
}

The hashCode value will differ if any of the field values differ.

如果任何一个字段的值不同,hashCode值将不同。

3.5. toString

3.5.toString

Lastly, we also receive a toString method that results in a string containing the name of the record, followed by the name of each field and its corresponding value in square brackets.

最后,我们还收到一个toString方法,该方法的结果是一个包含记录名称的字符串,后面是每个字段的名称以及方括号中的相应值

Therefore, instantiating a Person with a name of “John Doe” and an address of “100 Linda Ln.” results in the following toString result:

因此,实例化一个名字为“John Doe”、地址为“100 Linda Ln.“的Person,会产生以下toString结果。

Person[name=John Doe, address=100 Linda Ln.]

4. Constructors

4.构建者

While a public constructor is generated for us, we can still customize our constructor implementation.

虽然已经为我们生成了一个公共构造函数,但我们仍然可以定制我们的构造函数实现。

This customization is intended to be used for validation and should be kept as simple as possible.

该定制的目的是用于验证,应尽可能地保持简单。

For example, we can ensure that the name and address provided to our Person record aren’t null using the following constructor implementation:

例如,我们可以确保提供给我们的Person记录的nameaddress不是null,使用以下构造函数实现。

public record Person(String name, String address) {
    public Person {
        Objects.requireNonNull(name);
        Objects.requireNonNull(address);
    }
}

We can also create new constructors with different arguments by supplying a different argument list:

我们还可以通过提供不同的参数列表来创建具有不同参数的新构造函数。

public record Person(String name, String address) {
    public Person(String name) {
        this(name, "Unknown");
    }
}

As with class constructors, the fields can be referenced using the this keyword (for example, this.name and this.address), and the arguments match the names of the fields (that is, name and address).

与类构造函数一样,字段可以使用this关键字来引用(例如,this.namethis.address),并且参数与字段的名称相匹配(即,nameaddress)。

Note that creating a constructor with the same arguments as the generated public constructor is valid, but this requires that each field be manually initialized:

请注意,创建一个与生成的公共构造函数具有相同参数的构造函数是有效的,但这需要手动初始化每个字段

public record Person(String name, String address) {
    public Person(String name, String address) {
        this.name = name;
        this.address = address;
    }
}

Additionally, declaring a no-argument constructor and one with an argument list matching the generated constructor results in a compilation error.

此外,声明一个无参数的构造函数和一个参数列表与生成的构造函数匹配的构造函数会导致编译错误

Therefore, the following won’t compile:

因此,下面的内容不会被编译。

public record Person(String name, String address) {
    public Person {
        Objects.requireNonNull(name);
        Objects.requireNonNull(address);
    }
    
    public Person(String name, String address) {
        this.name = name;
        this.address = address;
    }
}

5. Static Variables & Methods

5.静态变量和方法

As with regular Java classes, we can also include static variables and methods in our records.

与普通的Java类一样,我们也可以在记录中包含静态变量和方法

We declare static variables using the same syntax as a class:

我们使用与类相同的语法来声明静态变量。

public record Person(String name, String address) {
    public static String UNKNOWN_ADDRESS = "Unknown";
}

Likewise, we declare static methods using the same syntax as a class:

同样地,我们使用与类相同的语法来声明静态方法。

public record Person(String name, String address) {
    public static Person unnamed(String address) {
        return new Person("Unnamed", address);
    }
}

We can then reference both static variables and static methods using the name of the record:

然后我们可以使用记录的名称来引用静态变量和静态方法。

Person.UNKNOWN_ADDRESS
Person.unnamed("100 Linda Ln.");

6. Conclusion

6.结语

In this article, we examined the record keyword introduced in Java 14, including the fundamental concepts and intricacies.

在这篇文章中,我们研究了Java 14中引入的record关键字,包括基本概念和错综复杂的内容。

Using records with their compiler-generated methods, we can reduce boilerplate code and improve the reliability of our immutable classes.

使用记录及其编译器生成的方法,我们可以减少模板代码,提高我们不可变的类的可靠性。

The code and examples for this article can be found over on GitHub.

本文的代码和示例可以在GitHub上找到over