JEP 384:记录(第2版预览)

官方原文(英文)地址: https://openjdk.java.net/jeps/384
个人原创翻译,转载请注明出处。

Summary

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编程语言,记录是充当不可变数据的透明载体的类。记录可以被视为名义化元组

History

Records were proposed by JEP 359 in mid 2019 and delivered in JDK 14 in early 2020 as a preview feature. This JEP proposes to re-preview the feature in JDK 15, both to incorporate refinements based on feedback and to support additional forms of local classes and interfaces in the Java language.

历史

记录是由JEP 359在2019年中期提出的,并在2020年初作为JDK 14预览特性。本JEP建议再次预览JDK 15中的特性,以结合基于反馈的改进,并支持Java语言中的局部类与接口。

Goals

  • Devise an object-oriented construct that expresses a simple aggregation of values.
  • Help programmers to focus on modeling immutable data rather than extensible behavior.
  • Automatically implement data-driven methods such as equals and accessors.
  • Preserve long-standing Java principles such as nominal typing and migration compatibility.

目标

  • 设计一个面向对象的构造,用来表示简单数据的聚合。
  • 帮助开发者专注于对不可变数据进行建模,而不是对可扩展的行为进行建模。
  • 自动实现数据驱动的方法,例如equals和访问器。
  • 保留长期的Java原则,例如名义类型与迁移兼容性。

Non-Goals

  • It is not a goal to declare a "war on boilerplate". In particular, it is not a goal to address the problems of mutable classes which use the JavaBeans naming conventions.
  • It is not a goal to add features such as properties or annotation-driven code generation, which are often proposed to streamline the declaration of classes for "Plain Old Java Objects".

非目标

  • 宣告“样板战争”不是目标;特别是,使用JavaBean命名规范来解决可变类的问题不是目标。
  • 添加如属性或注解驱动的代码生成也不是目标,通常建议使用这些功能来简化“Plain Old Java Objects”类的声明。

Motivation

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 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太冗长”或有“过多形式”。一些最严重的反例是那些仅仅作为少量值的不可变数据载体的类。可能写一个数据载体类会涉及到大量低价值的、重复的、易错的代码:构造器、访问器、equalshashCodetoString等等。例如,一个携带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 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, y, and z" 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会帮助在数据载体类中写出大部分代码,但不会做任何事情来帮助代码阅读者在几十行样板代码中提取“我是xyz的数据载体”的含义。编写简单聚合建模的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.

表面看来,用记录来减少样板代码很诱人,但我们选择了一个更加语义化的目标:用数据为数据建模。(如果语义正确,样板代码会自解释。)声明默认情况下数据不可变的数据载体类,并提供生成和使用数据的惯用方法,这应该非常简单明了。

Description

Records are a new kind of class in the Java language. The purpose of a record is to declare that a small group of variables is to be regarded as a new kind of entity. A record declares its state -- the group of variables -- and commits to an API that matches that state. This means that records give up a freedom that classes usually enjoy -- the ability to decouple a class's API from its internal representation -- but in return, records become significantly more concise.

描述

记录是Java语言中一种新的类。记录的目的是声明一小组变量,将其视为一种新的实体。记录声明它的状态——即一组变量——并提交可以匹配该状态的API。这意味着记录放弃了类的自由——将类的API与其内部表示分离的能力——作为回报,记录获得了很大程度的简洁性。

The declaration of a record specifies a name, a header, and a body. The header lists the components of the record, which are the variables that make up its state. (The 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 records make the semantic claim of being transparent carriers for their data, a record acquires many standard members automatically:

  • For each component in the header, two members: a 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;
  • A canonical constructor whose signature is the same as the header, and which assigns each private field to the corresponding argument from the new expression which instantiates the record;
  • equals and hashCode methods which say that two records are equal if they are of the same type and contain equal component values; and
  • A toString method that returns a string representation of all the record components along with their names.

因为记录在语义上声称是其数据的简单透明持有者,所以记录会自动获取许多标准成员:

  • 为头中的每一个组件生成两个成员:一个public的访问器方法,具有与组件相同的名称和返回类型,和一个private final的字段,具有与组件相同的类型。
  • 一个签名与头相同的规范化构造器,并使用初始化记录时new表达式中的参数为每一个private字段赋值。
  • equalshashCode方法,用来在两条记录的组件具有相同的类型和值时表示它们相等。
  • toString方法,用字符串表示记录中所有组件的名称。

In other words, the header of a record describes its state (the types and names of its components), and the API is derived mechanically and completely for 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包括用于构造、成员访问、判断相等和字符串显示的协议。(我们期待未来的版本支持解构模式,以实现强大的模式匹配。)

Rules for Records

Any of the members that are automatically derived from the header, with the exception of the private fields derived from the record components, can be declared explicitly. Any explicit implementation of accessors or equals/hashCode should be careful to preserve the semantic invariants of records.

记录的规则

所有根据记录头自动获取的成员,除了根据记录组件生成的private字段外,都可以被显式声明。所有显式声明的访问器或equalshashCode方法都应该注意保留记录的不变性语义。

The rules for constructors are different in a record than in a normal class. A normal class without any constructor declarations is automatically given a default constructor. In contrast, a record 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, or it may be declared in a more compact form that helps the developer focus on validating and normalizing parameters without the tedious work of assigning parameters to fields. A compact canonical constructor elides the list of formal parameters; they 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. For example, here is a compact canonical constructor that validates its (implicit) formal parameters:

规范化构造器可以被显式声明,具有能够匹配记录头的参数,如上所示,或者可以被声明为更紧凑的形式,以帮助开发者专注于验证和规范化参数,而无需做用参数为字段赋值的繁琐工作。一个紧凑的规范化构造器省略形式参数,它们被隐式声明,并且与记录组件相对应的private字段不能在记录体中赋值,而是自动地在构造器的末尾使用形式参数赋值(this.x = x;)。例如,这是一个验证其(隐式)形式参数的紧凑规范化构造器:

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));
    }
}

There are numerous restrictions on the declaration of a record:

  • A record does not have an extends clause. The superclass of a record is always java.lang.Record, similar to how the superclass of an enum is always java.lang.Enum. Even though a normal class can explicitly extend its implicit superclass Object, a record cannot explicitly extend any class, not even its implicit superclass Record.
  • A record is implicitly final, and cannot be abstract. These restrictions emphasize that the API of a record is defined solely by its state description, and cannot be enhanced later by another class or record.
  • A record cannot explicitly declare instance fields, and cannot contain instance initializers. These restrictions ensure that the record header alone defines the state of a record value.
  • The implicitly declared fields corresponding to the record components of a record class are final and moreover are not modifiable via reflection (doing so will throw IllegalAccessException). These restrictions embody an immutable by default policy that is widely applicable for data-carrier classes.
  • Any explicit declarations of a member that would otherwise be automatically derived must match the type of the automatically derived member exactly, disregarding type annotations on the explicit declaration.
  • A record cannot declare native methods. If a record could declare a native method, then the behavior of the record would by definition depend on external state rather than the record's explicit state. No class with native methods is to be a good candidate for migration to a record.

声明记录时有几点限制:

  • 记录不具有extends子句。记录的父类总是java.lang.Record,类似于一个枚举的父类总是java.lang.Enum。尽管一个普通类可以显式地继承隐式的父类Object,但记录不可以显式继承任何类,即使是它的隐式父类Record
  • 记录是隐式final的,不可以是abstract的。这些限制强调记录的API仅由其状态描述定义,并且以后不能由另一个类或记录进行增强。
  • 记录不可以显式定义实例字段,不可以包含构造块。这些限制可以确保仅记录头定义记录值的状态。
  • 记录中隐式声明的与组件对应的字段是final的,而且不能用反射进行修改(这样做将引发IllegalAccessException)。这些限制体现了一种默认不变策略,该策略广泛适用于数据载体类。
  • 任何不显式声明就会自动生成的成员,必须与自动生成的成员类型严格匹配,而不管显式声明上的类型注解。
  • 记录不能声明native方法。如果记录可以声明native方法,那么记录的表现将完全依赖于外部状态,而不是记录的显式状态。带有native方法的类都不适合迁移到记录。

Beyond the restrictions above, a record behaves like a normal class:

  • A record is instantiated with the new keyword.
  • A record can be declared top level or nested, and can be generic.
  • A record can declare static methods, static fields, and static initializers.
  • A record can declare instance methods. Namely, a record can explicitly declare public accessor methods which correspond to components, and can also declare other instance methods.
  • A record can implement interfaces. While a record cannot specify a superclass (because that would mean inherited state, beyond the state described in the header), a record can freely specify superinterfaces and declare instance methods to help implement them. Just as for classes, an interface can usefully characterize the behavior of many records; the behavior may be domain-independent (e.g., Comparable) or domain-specific, in which case records can be part of a sealed hierarchy which captures the domain (see below).
  • A record can declare nested types, including nested records. If a record is itself nested, then it is implicitly static; this avoids an immediately enclosing instance which would silently add state to the record.
  • A record, and the components in its state description, can be annotated. The annotations are propagated to the automatically derived fields, methods, and constructor parameters. Type annotations on the types of record components are also propagated to the types of the automatically derived members.

除了上述限制之外,记录的行为类似于普通类:

  • 记录使用new关键字进行初始化。
  • 记录可以被定义为顶级或嵌套的,可以是泛型的。
  • 记录可以声明静态方法、静态字段和静态初始化块。
  • 记录可以声明实例方法。即,记录可以显式声明与组件对应的public访问器方法,也可以声明其他实例方法。
  • 记录可以实现接口。尽管记录无法指定父类(因为这意味着继承的状态,超出了记录头中描述的状态),但是记录可以自由地指定父接口并声明实例方法来实现它们。就像类一样,接口可以有效地描述许多记录的行为。该行为可以是与域无关的(例如Comparable)或是与域相关的,在这种情况下记录可以是一个捕获域的密封层(见下文)。
  • 记录可以声明嵌套类型,包括嵌套记录。如果记录自身是嵌套的,那么它是隐式静态的;这避免了立即闭包的实例,该实例将以静默方式将状态添加到记录中。
  • 记录及其状态描述中的组件可以被注解修饰。注解将传播到自动派生的字段、方法和构造器参数上。记录组件类型上的类型注解也将会传播到派生成员的类型上。

Records and Sealed Types

Records work well with sealed types (JEP 360). For example, a family of records can 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 records and sealed types is sometimes referred to as algebraic data types. Records allow us to express product types, and sealed types allow us to express sum types.

记录与密封类型的组合有时被称为代数数据类型。记录使我们能够表示product类型,而记录类型使我们能够表示sum类型。

Local records

A program that produces and consumes records is likely to deal with many intermediate values that are themselves simple groups of variables. It will often be convenient to declare records to model those intermediate values. One option is to declare "helper" records 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, this JEP proposes local records, akin to the traditional construct of local classes.

局部记录

产生和使用记录的程序可能会处理许多本身就是简单变量组的中间值。声明记录以对那些中间值建模通常会很方便。一种选择是声明static且嵌套的“helper”记录,就像今天许多程序声明helper类一样。一个更方便的选择是在方法内部声明一条记录,靠近操作变量的代码。因此,本JEP提出了局部记录,类似于局部类的传统构造。

In the following example, the aggregation of a merchant and a monthly sales figure is modeled with a local record, MerchantSales. Using this record 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 records are a particular case of nested records. Like all nested records, local records 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. The fact that local records 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.

局部记录是嵌套记录的一种特例。像所有嵌套记录一样,局部记录是隐式静态的。这意味着他们自己的方法无法访问闭包方法的任何变量;反过来,这避免了捕获立即封闭的实例,该实例将以静默方式将状态添加到记录中。局部记录是隐式静态的,这与不是隐式静态的局部类相反。实际上,局部类永远不会是静态的——无论显式或隐式——而且总是可以访问闭包方法的变量。

Given the usefulness of local records, it would be useful to have local enums and local interfaces too. They were traditionally disallowed in Java because of concern over their semantics. Specifically, nested enums and nested interfaces are implicitly static, so local enums and local interfaces should be implicitly static too; yet, local declarations in the Java language (local variables, local classes) are never static. However, the introduction of local records in JEP 359 overcame this semantic concern, allowing a local declaration to be static, and opening the door to local enums and local interfaces.

考虑到局部记录的有用性,拥有局部枚举和局部接口也将很有用。因为担心它们的语义,所以传统上在Java中不允许使用它们。具体来说,嵌套枚举和嵌套接口是隐式静态的,因此局部枚举和局部接口也应该是隐式静态的。但是,Java语言中的局部声明(局部变量,局部类)永远不会是静态的。但是,在JEP 359中引入局部记录克服了这种语义上的顾虑,允许局部声明为静态,并为局部枚举和局部接口打开了大门。

Annotations on records

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 constructor parameter of the same name and type.

记录的注解

记录组件在记录声明中具有多个角色。记录组件是一级类的概念,但每个组件还对应于一个具有相同名称和类型的字段,一个具有相同名称和返回类型的访问器方法,以及一个具有相同名称和类型的构造器参数。

This raises the question, when a component is annotated, what actually is being annotated? And the answer is, "all of these that are applicable for this particular annotation." 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 and that it is applicable to a field declaration. 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 and that it is applicable to both a field declaration and a method declaration.

这声明了注解@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 programmer using the @Target meta-annotation. The propagation rules are systematic and intuitive, and all that apply are followed:

  • If an annotation on a record component is applicable to a field declaration, then the annotation appears on the corresponding private field.
  • If an annotation on a record component is applicable to a method declaration, then the annotation appears on the corresponding accessor method.
  • If an annotation on a record component is applicable to a formal parameter, then the annotation appears on the corresponding formal parameter of the canonical constructor if one is not declared explicitly, or to the corresponding formal parameter of the compact constructor if one is declared explicitly.
  • If an annotation on a record component is applicable to a type, the propagation rules are the same as for declaration annotations, except that the annotation is appears on the corresponding type use rather than declaration.

回到记录组件上的注解,这些注解出现在适用的相应程序点处。换句话说,其传播是在开发者使用@Target元注解的控制下进行的。传播规则是系统且直观的,并且遵循所有适用的规则:

  • 如果记录组件上的注解适用于字段声明,那么注解会出现在对应的private字段上。
  • 如果记录组件上的注解适用于方法声明,那么注解会出现在对应的访问器方法上。
  • 如果记录组件上的注解适用于形式参数,则如果未显式声明注解,则该注解将出现在规范构造器的对应形式参数上;如果显式声明,则注解将出现在紧凑构造器的对应形式参数上。
  • 如果记录组件上的注解适用于某种类型,则传播规则与声明注解相同,不同之处在于,该注解出现在对应的类型使用而不是声明上。

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的公共访问器方法或(非紧凑的)规范构造参数,那么它仅具有直接显式在其上的注解;没有任何内容从相应的记录组件传播到这些成员。

It is also possible to declare that an annotation came from one defined on a record component using a new annotation declaration @Target(RECORD_COMPONENT). These annotations can be retrieved by reflection as detailed in the Reflection API section below.

也可以使用新的注解声明@Target(RECORD_COMPONENT)声明来自记录组件上定义的注解。这些注解可以通过反射操作,如下面的“反射API”部分所述。

Java Grammar

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:
 {Annotation} {ConstructorModifier} SimpleTypeName ConstructorBody

Java语法

记录声明:
  {类修饰符} `record` 类型标识符 [类型参数]
    记录头 [父接口] 记录体
  
记录头:
 `(` [记录组件列表] `)`
  
记录组件列表:
 记录组件 { `,` 记录组件}
  
记录组件:
 {注解} UnannType 标识符
 可变数量的记录组件
  
可变数量的记录组件:
 {注解} UnannType {注解} `...` 标识符

记录体:
  `{` {记录体声明} `}`
  
记录体声明:
  类体声明
  紧凑构造器声明
  
紧凑构造器声明:
 {注解} {构造器修饰符} 简单类名称 构造器体

Class-file representation

The class file of a record uses a Record attribute to store information about the record's components:

类文件表示

记录的class文件使用一个Record属性来存储关于记录的组件信息:

Java Grammar

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];
}

Java语法

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, there must be a Signature attribute in the record_component_info structure.

如果记录组件具有与已擦除描述符不同的泛型签名,那么在record_component_info结构中必须有Signature属性。

Reflection API

The following public methods will be added to java.lang.Class:

  • RecordComponent[] getRecordComponents()
  • boolean isRecord()

反射API

下面的公有方法将被添加到java.lang.Class中:

  • RecordComponent[] getRecordComponents()
  • boolean isRecord()

The method 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 in the array, including its name, annotations, and accessor method.

getRecordComponents()方法返回一个java.lang.reflect.RecordComponent对象的数组。该数组的元素对应于记录的组件,与它们在记录声明中的顺序相同。可以从数组中的每个元素提取附加信息,包括其名称、注解和访问器方法。

The method isRecord returns true if the given class was declared as a record. (Compare with isEnum.)

如果给定的类是以记录声明的,那么isRecord方法返回true。(就像isEnum。)

Alternatives

Records can be considered a nominal form of tuples. Instead of records, we could implement structural tuples. However, while tuples might offer a lighter weight means of expressing some aggregates, the result is often inferior aggregates:

  • A central aspect of Java's design philosophy is that names matter. Classes and their members have meaningful names, while tuples and tuple components do not. That is, a Person class with properties firstName and lastName is clearer and safer than an anonymous tuple of String and String.
  • Classes support state validation through their constructors; tuples do not. Some data aggregates (such as numeric ranges) have invariants that, if enforced by the constructor, can thereafter be relied upon; tuples do not offer this ability.
  • Classes can have behavior that is based on their state; co-locating the state and behavior makes the behavior more discoverable and easier to access. Tuples, being raw data, offer no such facility.

备选方案

记录可以视为元组的名义形式。作为记录的替代,我们可以实现结构化元组。但是,虽然元组可以提供表示某些聚合的轻量级方法,但结果通常是劣等的聚合:

  • Java哲学的核心是名称很重要。类及其成员具有有意义的名称,而元组和元组的组件却没有。也就是说,具有firstNamelastName属性的Person类,要比具有StringString的匿名元组更为清晰和安全。
  • 类通过构造器支持状态验证;元组则不然。一些数据聚合(如数字范围)具有不变量,如果由构造器强制执行,则以后可以依赖这些不变量;元组则不提供此功能。
  • 类可以具有基于其状态的行为。将状态和行为并列放置可以使行为更易于发现和访问。元组是原始数据,不提供此类功能。

Dependencies

In addition to the combination of records and sealed types mentioned above, records lend themselves naturally to pattern matching. Because records couple their API to their state description, we will eventually be able to derive deconstruction patterns for records as well, and use sealed type information to determine exhaustiveness in switch expressions with type patterns or deconstruction patterns.

依赖

作为对记录类与密封类型组合的补充,记录将自身借给模式匹配。因为记录将其API与其状态描述相结合,所以我们最终也将能够导出记录的解构模式,并使用密封类型的信息来确定具有类型模式或解构模式的switch表达式的穷举性。