官方原文(英文)地址:
https://openjdk.java.net/jeps/395
个人原创翻译,转载请注明出处。
Enhance the Java programming language with records, which are classes that act as transparent carriers for immutable data. Records can be thought of as nominal tuples.
通过记录增强Java编程语言,记录是充当不可变数据的透明载体的类。记录可以被视为名义化元组。
Records were proposed by JEP 359 and delivered in JDK 14 as a preview feature.
In response to feedback, the design was refined by JEP 384 and delivered in JDK 15 as a preview feature for a second time. The refinements for the second preview were as follows:
public
. In the second preview, if the canonical constructor is implicitly declared then its access modifier is the same as the record class; if the canonical constructor is explicitly declared then its access modifier must provide at least as much access as the record class.@Override
annotation was extended to include the case where the annotated method is an explicitly declared accessor method for a record component.This JEP proposes to finalize the feature in JDK 16, with the following refinement:
这个JEP的目标是在JDK 16中完成该特性,并进行以下改进:
Additional refinements may be incorporated based on further feedback.
可以根据进一步的反馈合并其他改进。
equals
and accessors.equals
和访问器。It is a common complaint that "Java is too verbose" or has "too much ceremony". Some of the worst offenders are classes that are nothing more than immutable data carriers for a handful of values. Properly writing such a data-carrier class involves a lot of low-value, repetitive, error-prone code: constructors, accessors, equals
, hashCode
, toString
, etc. For example, a class to carry x and y coordinates inevitably ends up like this:
人们普遍抱怨“Java太冗长”或有“过多形式”。一些最严重的反例是那些仅仅作为少量值的不可变数据载体的类。可能写一个数据载体类会涉及到大量低价值的、重复的、易错的代码:构造器、访问器、equals
、hashCode
和toString
,等等。例如,一个携带x和y坐标的类不可避免地会写成这样:
class Point {
private final int x;
private final int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
int x() { return x; }
int y() { return y; }
public boolean equals(Object o) {
if (!(o instanceof Point)) return false;
Point other = (Point) o;
return other.x == x && other.y == y;
}
public int hashCode() {
return Objects.hash(x, y);
}
public String toString() {
return String.format("Point[x=%d, y=%d]", x, y);
}
}
class Point {
private final int x;
private final int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
int x() { return x; }
int y() { return y; }
public boolean equals(Object o) {
if (!(o instanceof Point)) return false;
Point other = (Point) o;
return other.x == x && other.y == y;
}
public int hashCode() {
return Objects.hash(x, y);
}
public String toString() {
return String.format("Point[x=%d, y=%d]", x, y);
}
}
Developers are sometimes tempted to cut corners by omitting methods such as equals
, leading to surprising behavior or poor debuggability, or by pressing an alternate but not entirely appropriate class into service because it has the "right shape" and they don't want to declare yet another class.
开发者有时会想要偷工减料,省略这些重要的equals
等方法,这会导致意外行为或难于调试;或者用一个并不完全合适的类投入服务,因为它具有“正确的形状”,而他们不想再另外声明个类。
IDEs help us to write most of the code in a data-carrier class, but don't do anything to help the reader distill the design intent of "I'm a data carrier for x
and y
" from the dozens of lines of boilerplate. Writing Java code that models a handful of values should be easier to write, to read, and to verify as correct.
IDE会帮助在数据载体类中写出大部分代码,但不会做任何事情来帮助代码阅读者在几十行样板代码中提取“我是x
和y
的数据载体”的含义。编写简单聚合建模的Java代码,应该更容易地——写、读和验证正确性。
While it is superficially tempting to treat records as primarily being about boilerplate reduction, we instead choose a more semantic goal: modeling data as data. (If the semantics are right, the boilerplate will take care of itself.) It should be easy and concise to declare data-carrier classes that by default make their data immutable and provide idiomatic implementations of methods that produce and consume the data.
表面看来,用记录来减少样板代码很诱人,但我们选择了一个更加语义化的目标:用数据为数据建模。(如果语义正确,样板代码会自解释。)声明默认情况下数据不可变的数据载体类,并提供生成和使用数据的惯用方法,这应该非常简单明了。
Record classes are a new kind of class in the Java language. Record classes help to model plain data aggregates with less ceremony than normal classes.
记录是Java语言中一种新的类。记录类用比普通类更少的形式代码,来帮助对数据聚合进行建模。
The declaration of a record class primarily consists of a declaration of its state; the record class then commits to an API that matches that state. This means that record classes give up a freedom that classes usually enjoy — the ability to decouple a class's API from its internal representation — but, in return, record class declarations become significantly more concise.
记录类的定义主要由它的状态组成;记录类会生成与状态匹配的API。这意味着状态类放弃了类的自由——将类的API与其内部表示分离的能力——但作为回报,记录类的定义获得了很大程度的简洁性。
More precisely, a record class declaration consists of a name, optional type parameters, a header, and a body. The header lists the components of the record class, which are the variables that make up its state. (This list of components is sometimes referred to as the state description.) For example:
更加精确地说,记录类的定义由一个名称、可选的类型参数、一个头和一个体组成。头的部分列出了记录的组件,也就是表示状态的变量。(组件列表有时也被称为状态描述。)例如:
record Point(int x, int y) { }
record Point(int x, int y) { }
Because record classes make the semantic claim of being transparent carriers for their data, a record class acquires many standard members automatically:
public
accessor method with the same name and return type as the component, and a private
final
field with the same type as the component;new
expression which instantiates the record;equals
and hashCode
methods which ensure that two record values are equal if they are of the same type and contain equal component values; andtoString
method that returns a string representation of all the record components, along with their names.因为记录类在语义上声称是其数据的简单透明持有者,所以记录会自动获取许多标准成员:
public
的访问器方法,具有与组件相同的名称和返回类型,和一个private final
的字段,具有与组件相同的类型。new
表达式中的参数为每一个私有字段赋值。equals
和hashCode
方法,用来在两条记录的组件具有相同的类型和值时表示它们相等。toString
方法,用字符串表示记录中所有组件的名称。In other words, the header of a record class describes its state, i.e., the types and names of its components, and the API is derived mechanically and completely from that state description. The API includes protocols for construction, member access, equality, and display. (We expect a future version to support deconstruction patterns to allow powerful pattern matching.)
换句话说,记录的头描述了它的状态,也就是组件的类型和名称,并根据状态描述机械地导出了API。这些API包括用于构造、成员访问、判断相等和字符串显示的协议。(我们期待未来的版本支持解构模式,以实现强大的模式匹配。)
The rules for constructors in a record class are different than in a normal class. A normal class without any constructor declarations is automatically given a default constructor. In contrast, a record class without any constructor declarations is automatically given a canonical constructor that assigns all the private fields to the corresponding arguments of the new
expression which instantiated the record. For example, the record declared earlier — record Point(int x, int y) { }
— is compiled as if it were:
在构造器的规则方面,记录和普通的类有所不同。没有任何构造器声明的普通类会自动给出一个默认构造器。不同的是,没有任何构造器声明的记录会自动给出一个规范化构造器,用初始化记录时new
表达式的参数为所有private字段赋值。例如,此前声明的记录——record Point(int x, int y) { }
——会被编译为如:
record Point(int x, int y) {
// Implicitly declared fields
private final int x;
private final int y;
// Other implicit declarations elided ...
// Implicitly declared canonical constructor
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
record Point(int x, int y) {
// 隐式声明字段
private final int x;
private final int y;
// 省略其他隐式声明 ...
// 隐式声明规范化构造器
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
The canonical constructor may be declared explicitly with a list of formal parameters which match the record header, as shown above. It may also be declared more compactly, by eliding the list of formal parameters. In such a compact canonical constructor the parameters are declared implicitly, and the private fields corresponding to record components cannot be assigned in the body but are automatically assigned to the corresponding formal parameter (this.x = x;
) at the end of the constructor. The compact form helps developers focus on validating and normalizing parameters without the tedious work of assigning parameters to fields.
规范化构造器可以被显式声明,具有能够匹配记录头的参数,如上所示。也可以声明为更紧凑的形式,省略形式参数列表。这样一个紧凑的规范化构造器的参数是隐式声明的,并且与记录组件相对应的private字段不能在记录体中赋值,而是自动地在构造器的末尾使用形式参数赋值(this.x = x;
)。紧凑形式帮助开发者专注于验证和规范化参数,而无需做用参数为字段赋值的繁琐工作。
For example, here is a compact canonical constructor that validates its implicit formal parameters:
例如,这是一个验证其(隐式)形式参数的紧凑规范化构造器:
record Range(int lo, int hi) {
Range {
if (lo > hi) // referring here to the implicit constructor parameters
throw new IllegalArgumentException(String.format("(%d,%d)", lo, hi));
}
}
record Range(int lo, int hi) {
Range {
if (lo > hi) // 这里引用隐式构造参数
throw new IllegalArgumentException(String.format("(%d,%d)", lo, hi));
}
}
Here is a compact canonical constructor that normalizes its formal parameters:
下面是一个紧凑的规范化构造器在正常化它的形式参数:
record Rational(int num, int denom) {
Rational {
int gcd = gcd(num, denom);
num /= gcd;
denom /= gcd;
}
}
record Rational(int num, int denom) {
Rational {
int gcd = gcd(num, denom);
num /= gcd;
denom /= gcd;
}
}
This declaration is equivalent to the conventional constructor form:
这个声明等效于传统的构造器形式:
record Rational(int num, int denom) {
Rational(int num, int demon) {
// Normalization
int gcd = gcd(num, denom);
num /= gcd;
denom /= gcd;
// Initialization
this.num = num;
this.denom = denom;
}
}
record Rational(int num, int denom) {
Rational(int num, int demon) {
// 正常化
int gcd = gcd(num, denom);
num /= gcd;
denom /= gcd;
// 初始化
this.num = num;
this.denom = denom;
}
}
Record classes with implicitly declared constructors and methods satisfy important, and intuitive, semantic properties. For example, consider a record class R
declared as follows:
具有隐式声明的构造器和方法的记录类满足重要且直观的语义属性。例如,考虑如下声明的记录类R
:
record R(T1 c1, ..., Tn cn) { }
record R(T1 c1, ..., Tn cn) { }
If an instance r1
of R
is copied in the following way:
R
的一个实例r1
以这种形式被拷贝:
R r2 = new R(r1.c1(), r1.c2(), ..., r1.cn());
R r2 = new R(r1.c1(), r1.c2(), ..., r1.cn());
then, assuming r1
is not the null reference, it is always the case that the expression r1.equals(r2)
will evaluate to true
. Explicitly declared accessor and equals
methods should respect this invariant. However, it is not generally possible for a compiler to check that explicitly declared methods respect this invariant.
那么,假设r1
不是null引用,则表达式r1.equals(r2)
总会等于true
。显式声明的访问器和equals
方法应该遵循这个约束。但是,编译器通常不可能检查显式声明的方法是否遵守了这个约束。
As an example, the following declaration of a record class should be considered bad style because its accessor methods "silently" adjust the state of a record instance, and the invariant above is not satisfied:
作为一个例子,下面的记录类声明应该被认为是糟糕的风格,因为它的访问器方法“静默地”调整了记录实例的状态,并且不满足上面的约束:
record SmallPoint(int x, int y) {
public int x() { return this.x < 100 ? this.x : 100; }
public int y() { return this.y < 100 ? this.y : 100; }
}
record SmallPoint(int x, int y) {
public int x() { return this.x < 100 ? this.x : 100; }
public int y() { return this.y < 100 ? this.y : 100; }
}
In addition, for all record classes the implicitly declared equals
method is implemented so that it is reflexive and that it behaves consistently with hashCode
for record classes that have floating point components. Again, explicitly declared equals
and hashCode
methods should behave similarly.
此外,对于所有记录类,都实现了隐式声明的equals
方法,以便使其是自反的,并且对于具有浮点组件的记录类,它的行为与hashCode
方法一致。同样,显式声明的equals
和hashCode
方法的行为应该类似。
There are numerous restrictions on the declaration of a record class in comparison to a normal class:
extends
clause. The superclass of a record class is always java.lang.Record
, similar to how the superclass of an enum class is always java.lang.Enum
. Even though a normal class can explicitly extend its implicit superclass Object
, a record cannot explicitly extend any class, even its implicit superclass Record
.final
, and cannot be abstract
. These restrictions emphasize that the API of a record class is defined solely by its state description, and cannot be enhanced later by another class.final
. This restriction embodies an immutable by default policy that is widely applicable for data-carrier classes.equals
or hashCode
methods should be careful to preserve the semantic invariants of the record class.native
methods. If a record class could declare a native
method then the behavior of the record class would by definition depend on external state rather than the record class's explicit state. No class with native methods is likely to be a good candidate for migration to a record.与普通的类进行比较,声明记录类时有几点限制:
extends
子句。记录的父类总是java.lang.Record
,类似于一个枚举的父类总是java.lang.Enum
。尽管一个普通类可以显式地继承隐式的父类Object
,但记录不可以显式继承任何类,即使是它的隐式父类Record
。final
的,不可以是abstract
的。这些限制强调记录的API仅由其状态描述定义,并且以后不能由另一个类进行增强。final
的。这条限制体现了广泛适用于数据载体类的默认不可变策略。equals
或hashCode
方法的显式实现,都应该谨慎地保留记录类的语义约束。native
方法。如果记录可以声明native
方法,那么记录的表现将完全依赖于外部状态,而不是记录的显式状态。带有native方法的类都不适合迁移到记录。Beyond the restrictions above, a record class behaves like a normal class:
new
expression.static
methods, fields, and initializers.Comparable
) or domain-specific, in which case records can be part of a sealed hierarchy which captures the domain (see below).writeObject
, readObject
, readObjectNoData
, writeExternal
, or readExternal
methods. The components of a record class govern serialization, while the canonical constructor of a record class governs deserialization.除了上述限制之外,记录的行为类似于普通类:
new
表达式进行创建。static
的方法、字段和初始化块。Comparable
)或是与域相关的,在这种情况下记录可以是一个捕获域的密封层(见下文)。writeObject
、readObject
、readObjectNoData
、writeExternal
或readExternal
方法来自定义处理。记录类的组件控制序列化,而记录类的规范化构造器控制反序列化。A program that produces and consumes instances of a record class is likely to deal with many intermediate values that are themselves simple groups of variables. It will often be convenient to declare record classes to model those intermediate values. One option is to declare "helper" record classes that are static and nested, much as many programs declare helper classes today. A more convenient option would be to declare a record inside a method, close to the code which manipulates the variables. Accordingly we define local record classes, akin to the existing construct of local classes.
产生和使用记录类实例的程序可能会处理许多本身就是简单变量组的中间值。声明记录以对那些中间值建模通常会很方便。一种选择是声明静态且嵌套的“helper”记录,就像今天许多程序声明helper类一样。一个更方便的选择是在方法内部声明一条记录,靠近操作变量的代码。因此,本JEP提出了局部记录类,类似于局部类的传统构造。
In the following example, the aggregation of a merchant and a monthly sales figure is modeled with a local record class, MerchantSales
. Using this record class improves the readability of the stream operations which follow:
在下面的例子中,使用局部记录类MerchantSales
对商人和每月销售额进行汇总。使用该记录可以提高以下流操作的可读性:
List findTopMerchants(List merchants, int month) {
// Local record
record MerchantSales(Merchant merchant, double sales) {}
return merchants.stream()
.map(merchant -> new MerchantSales(merchant, computeSales(merchant, month)))
.sorted((m1, m2) -> Double.compare(m2.sales(), m1.sales()))
.map(MerchantSales::merchant)
.collect(toList());
}
List findTopMerchants(List merchants, int month) {
// 局部记录
record MerchantSales(Merchant merchant, double sales) {}
return merchants.stream()
.map(merchant -> new MerchantSales(merchant, computeSales(merchant, month)))
.sorted((m1, m2) -> Double.compare(m2.sales(), m1.sales()))
.map(MerchantSales::merchant)
.collect(toList());
}
Local record classes are a particular case of nested record classes. Like nested record classes, local record classes are implicitly static. This means that their own methods cannot access any variables of the enclosing method; in turn, this avoids capturing an immediately enclosing instance which would silently add state to the record class. The fact that local record classes are implicitly static is in contrast to local classes, which are not implicitly static. In fact, local classes are never static — implicitly or explicitly — and can always access variables in the enclosing method.
局部记录类是嵌套记录类的一种特例。像所有嵌套记录一样,局部记录是隐式静态的。这意味着他们自己的方法无法访问闭包方法的任何变量;反过来,这避免了捕获立即封闭的实例,该实例将以静默方式将状态添加到记录中。局部记录是隐式静态的,这与不是隐式静态的局部类相反。实际上,局部类永远不会是静态的——无论显式或隐式——而且总是可以访问闭包方法的变量。
The addition of local record classes is an opportunity to add other kinds of implicitly-static local declarations.
添加局部记录类是一个机会,可以添加其他隐式静态的局部声明。
Nested enum classes and nested interfaces are already implicitly static, so for consistency we define local enum classes and local interfaces, which are also implicitly static.
嵌套枚举类和嵌套接口已经是隐式静态的,所以为了一致,我们定义局部枚举类和局部接口也是隐式静态的。
It is currently specified to be a compile-time error if an inner class declares a member that is explicitly or implicitly static, unless the member is a constant variable. This means that, for example, an inner class cannot declare a record class member, since nested record classes are implicitly static.
如果一个内部类声明了一个显式或隐式静态的成员,那么当前会认定为编译错误,除非该成员是一个常量。这意味着,例如,内部类不能声明记录类成员,因为嵌套的记录类是隐式静态的。
We relax this restriction in order to allow an inner class to declare members that are either explicitly or implicitly static. In particular, this allows an inner class to declare a static member that is a record class.
我们放宽了这个限制,以允许内部类声明显式或隐式静态的成员。特别是,这允许内部类声明作为记录类的静态成员。
Record components have multiple roles in record declarations. A record component is a first-class concept, but each component also corresponds to a field of the same name and type, an accessor method of the same name and return type, and a formal parameter of the canonical constructor of the same name and type.
记录组件在记录声明中具有多个角色。记录组件是一类(first-class)的概念,但每个组件还对应于一个具有相同名称和类型的字段,一个具有相同名称和返回类型的访问器方法,以及一个具有相同名称和类型的规范化构造器的形式参数。
This raises the question: When a component is annotated, what actually is being annotated? The answer is, "all of the elements to which this particular annotation is applicable." This enables classes that use annotations on their fields, constructor parameters, or accessor methods to be migrated to records without having to redundantly declare these members. For example, a class such as the following
这就带来了一个问题:当对组件进行注解时,实际上是在注解什么?答案是:“所有适用于该注解的内容”。这就允许那些在其字段、构造器参数或访问器方法上使用注解的类迁移到记录,而不必冗余地声明这些成员。例如,下面的类:
public final class Card {
private final @MyAnno Rank rank;
private final @MyAnno Suit suit;
@MyAnno Rank rank() { return this.rank; }
@MyAnno Suit suit() { return this.suit; }
...
}
public final class Card {
private final @MyAnno Rank rank;
private final @MyAnno Suit suit;
@MyAnno Rank rank() { return this.rank; }
@MyAnno Suit suit() { return this.suit; }
...
}
can be migrated to the equivalent, and considerably more readable, record declaration:
可以迁移到等效的、更加便捷、更加可读的记录声明:
public record Card(@MyAnno Rank rank, @MyAnno Suit suit) { ... }
public record Card(@MyAnno Rank rank, @MyAnno Suit suit) { ... }
The applicability of an annotation is declared using a @Target
meta-annotation. Consider the following:
注解的适用性使用@Target
元注解声明。考虑下面的:
@Target(ElementType.FIELD)
public @interface I1 {...}
@Target(ElementType.FIELD)
public @interface I1 {...}
This declares the annotation @I1
that it is applicable to field declarations. We can declare that an annotation is applicable to more than one declaration; for example:
这声明了注解@I1,适用于字段声明。我们可以声明一个注解适用于多个声明,例如:
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface I2 {...}
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface I2 {...}
This declares an annotation @I2
that it is applicable to both field declarations and method declarations.
这声明了注解@I2
,它可以同时适用于字段声明和方法声明。
Returning to annotations on a record component, these annotations appear at the corresponding program points where they are applicable. In other words, the propagation is under the control of the developer using the @Target
meta-annotation. The propagation rules are systematic and intuitive, and all that apply are followed:
回到记录组件上的注解,这些注解出现在适用的相应程序点处。换句话说,其传播是在开发者使用@Target
元注解的控制下进行的。传播规则是系统且直观的,并且遵循所有适用的规则:
If a public accessor method or (non-compact) canonical constructor is declared explicitly, then it only has the annotations which appear on it directly; nothing is propagated from the corresponding record component to these members.
如果显式声明了public的公共访问器方法或(非紧凑的)规范构造参数,那么它仅具有直接显式在其上的注解;没有任何内容从相应的记录组件传播到这些成员。
A declaration annotation on a record component will not be amongst those associated with the record component at run time via the reflection API unless the annotation is meta-annotated with @Target(RECORD_COMPONENT)
.
在记录组件上声明的注解,不会出现在那些在运行时通过反射API获取记录组件关联的注解当中,除非该注解是用@Target(RECORD_COMPONENT)
标注的。
The abstract class java.lang.Record
is the common superclass of all record classes. Every Java source file implicitly imports the java.lang.Record
class, as well as all other types in the java.lang
package, regardless of whether you enable or disable preview features. However, if your application imports another class named Record
from a different package, you might get a compiler error.
抽象类java.lang.Record
是所有记录类的共同父类。每个Java源文件都隐式导入java.lang.Record
类,正如java.lang
包中所有的其他类型那样,无论你启用或禁用预览特性。然而,如果你的应用导入了另一个包中名叫Record
的类,你会得到一个编译错误。
Consider the following class declaration of com.myapp.Record
:
考虑下面com.myapp.Record
类的定义:
package com.myapp;
public class Record {
public String greeting;
public Record(String greeting) {
this.greeting = greeting;
}
}
package com.myapp;
public class Record {
public String greeting;
public Record(String greeting) {
this.greeting = greeting;
}
}
The following example, org.example.MyappPackageExample
, imports com.myapp.Record
with a wildcard but doesn't compile:
下面的例子,org.example.MyappPackageExample
,用通配符导入了com.myapp.Record
但无法编译:
package org.example;
import com.myapp.*;
public class MyappPackageExample {
public static void main(String[] args) {
Record r = new Record("Hello world!");
}
}
package org.example;
import com.myapp.*;
public class MyappPackageExample {
public static void main(String[] args) {
Record r = new Record("Hello world!");
}
}
The compiler generates an error message similar to the following:
编译器生成的错误信息类似于这样:
./org/example/MyappPackageExample.java:6: error: reference to Record is ambiguous
Record r = new Record("Hello world!");
^
both class com.myapp.Record in com.myapp and class java.lang.Record in java.lang match
./org/example/MyappPackageExample.java:6: error: reference to Record is ambiguous
Record r = new Record("Hello world!");
^
both class com.myapp.Record in com.myapp and class java.lang.Record in java.lang match
./org/example/MyappPackageExample.java:6: error: reference to Record is ambiguous
Record r = new Record("Hello world!");
^
both class com.myapp.Record in com.myapp and class java.lang.Record in java.lang match
./org/example/MyappPackageExample.java:6: error: reference to Record is ambiguous
Record r = new Record("Hello world!");
^
both class com.myapp.Record in com.myapp and class java.lang.Record in java.lang match
Both Record
in the com.myapp
package and Record
in the java.lang
package are imported with wildcards. Consequently, neither class takes precedence, and the compiler generates an error message when it encounters the use of the simple name Record
.
在com.myapp
包中的Record
和在java.lang
包中的Record
,二者都被导入了。因此,两个类都没有优先权,编译器在遇到使用短名称Record
时会生成错误信息。
To enable this example to compile, the import
statement can be changed so that it imports the fully qualified name of Record
:
想要让这个例子可以编译,import
语句可以改成导入Record
的完全限定名:
import com.myapp.Record;
import com.myapp.Record;
The introduction of classes in the java.lang
package is rare but sometimes necessary. Previous examples are Enum
in Java 5, Module
in Java 9, and Record
in Java 14.
java.lang
包中类的引入很少见,但有时是必要的。之前的例子是Java 5中的Enum
、Java 9中的Module
和Java 14中的Record
。
RecordDeclaration:
{ClassModifier} `record` TypeIdentifier [TypeParameters]
RecordHeader [SuperInterfaces] RecordBody
RecordHeader:
`(` [RecordComponentList] `)`
RecordComponentList:
RecordComponent { `,` RecordComponent}
RecordComponent:
{Annotation} UnannType Identifier
VariableArityRecordComponent
VariableArityRecordComponent:
{Annotation} UnannType {Annotation} `...` Identifier
RecordBody:
`{` {RecordBodyDeclaration} `}`
RecordBodyDeclaration:
ClassBodyDeclaration
CompactConstructorDeclaration
CompactConstructorDeclaration:
{ConstructorModifier} SimpleTypeName ConstructorBody
记录声明:
{类修饰符} `record` 类型标识符 [类型参数]
记录头 [父接口] 记录体
记录头:
`(` [记录组件列表] `)`
记录组件列表:
记录组件 { `,` 记录组件}
记录组件:
{注解} UnannType 标识符
可变数量的记录组件
可变数量的记录组件:
{注解} UnannType {注解} `...` 标识符
记录体:
`{` {记录体声明} `}`
记录体声明:
类体声明
紧凑构造器声明
紧凑构造器声明:
{构造器修饰符} 简单类名称 构造器体
The class
file of a record uses a Record
attribute to store information about the record's components:
记录的class
文件使用一个Record
属性来存储关于记录的组件信息:
Record_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 components_count;
record_component_info components[components_count];
}
record_component_info {
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
Record_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 components_count;
record_component_info components[components_count];
}
record_component_info {
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
If the record component has a generic signature that is different from the erased descriptor then there must be a Signature
attribute in the record_component_info
structure.
如果记录组件具有与已擦除描述符不同的泛型签名,那么在record_component_info
结构中必须有Signature
属性。
We add two public methods to java.lang.Class
:
RecordComponent[] getRecordComponents()
— Returns an array of java.lang.reflect.RecordComponent
objects. The elements of this array correspond to the record's components, in the same order as they appear in the record declaration. Additional information can be extracted from each element of the array, including its name, annotations, and accessor method.boolean isRecord()
— Returns true if the given class was declared as a record. (Compare with isEnum
.)我们添加两个公有方法到java.lang.Class中:
RecordComponent[] getRecordComponents()
:返回一个java.lang.reflect.RecordComponent
对象的数组。该数组的元素对应于记录的组件,与它们在记录声明中的顺序相同。可以从数组中的每个元素提取附加信息,包括其名称、注解和访问器方法。boolean isRecord()
:如果给定的类是以记录声明的,则返回true。(就像isEnum
。)Record classes can be considered a nominal form of tuples. Instead of record classes, we could implement structural tuples. However, while tuples might offer a lightweight means of expressing some aggregates, the result is often inferior aggregates:
Person
record class with components firstName
and lastName
is clearer and safer than an anonymous tuple of two strings.记录类可以视为元组的名义形式。作为记录的替代,我们可以实现结构化元组。但是,虽然元组可以提供表示某些聚合的轻量级方法,但结果通常是劣等的聚合:
firstName
和lastName
属性的Person
类,要比具有两个字符串的匿名元组更为清晰和安全。Record classes work well with another feature currently in preview, namely sealed classes (JEP 360). For example, a family of record classes can be explicitly declared to implement the same sealed interface:
记录类可以很好地与另一个当前独揽的特性一起工作,称为密封类型(JEP 360)。例如,同一家族的记录类可以被显式定义实现同一个密封接口:
package com.example.expression;
public sealed interface Expr
permits ConstantExpr, PlusExpr, TimesExpr, NegExpr {...}
public record ConstantExpr(int i) implements Expr {...}
public record PlusExpr(Expr a, Expr b) implements Expr {...}
public record TimesExpr(Expr a, Expr b) implements Expr {...}
public record NegExpr(Expr e) implements Expr {...}
package com.example.expression;
public sealed interface Expr
permits ConstantExpr, PlusExpr, TimesExpr, NegExpr {...}
public record ConstantExpr(int i) implements Expr {...}
public record PlusExpr(Expr a, Expr b) implements Expr {...}
public record TimesExpr(Expr a, Expr b) implements Expr {...}
public record NegExpr(Expr e) implements Expr {...}
The combination of record classes and sealed classes is sometimes referred to as algebraic data types. Record classes allow us to express products, and sealed classes allow us to express sums.
记录类与密封类型的组合有时被称为代数数据类型。记录类使我们能够表示product类型,而记录类型使我们能够表示sum类型。
In addition to the combination of record classes and sealed classes, record classes lend themselves naturally to pattern matching. Because record classes couple their API to their state description, we will eventually be able to derive deconstruction patterns for record classes as well, and use sealed type information to determine exhaustiveness in switch
expressions with type patterns or deconstruction patterns.
作为对记录类与密封类型组合的补充,记录将自身借给模式匹配。因为记录将其API与其状态描述相结合,所以我们最终也将能够导出记录的解构模式,并使用密封类型的信息来确定具有类型模式或解构模式的switch
表达式的穷举性。