JDK-8242303 : JEP 384: Records (Second Preview)
  • Type: JEP
  • Component: specification
  • Sub-Component: language
  • Priority: P3
  • Status: Closed
  • Resolution: Delivered
  • Fix Versions: 15
  • Submitted: 2020-04-07
  • Updated: 2022-03-11
  • Resolved: 2020-08-06
Related Reports
Relates :  
Relates :  
Sub Tasks
JDK-8242478 :  
JDK-8246157 :  
JDK-8246158 :  
Description
Summary
--------

Enhance the Java programming language with [records][records], which are classes that act as transparent carriers for immutable data. Records can be thought of as _nominal tuples_. This is a [preview language feature](http://openjdk.java.net/jeps/12) in JDK 15.

[records]: https://cr.openjdk.java.net/~briangoetz/amber/datum.html


History
--------

Records were proposed by [JEP 359](https://openjdk.java.net/jeps/359) in mid 2019 and delivered in [JDK 14](https://openjdk.java.net/projects/jdk/14) in early 2020 as a [preview feature](http://openjdk.java.net/jeps/12). 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.


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.

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".


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:

```
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.

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.

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.

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) { }
```

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.

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.)

### 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.

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](https://docs.oracle.com/javase/specs/jls/se14/html/jls-8.html#jls-8.8.9)_. 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:

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

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:

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

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.

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.


### Records and Sealed Types

Records work well with _sealed types_ ([JEP 360](https://openjdk.java.net/jeps/360)). For example, a family of records can implement the same sealed interface:

```
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_](https://en.wikipedia.org/wiki/Algebraic_data_type). Records allow us to express _product types_, and sealed types allow us to express _sum types_.


### 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](https://docs.oracle.com/javase/specs/jls/se14/html/jls-14.html#jls-14.3).

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:

```
List<Merchant> findTopMerchants(List<Merchant> 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());
}
```

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](http://openjdk.java.net/jeps/359) overcame this semantic concern, allowing a local declaration to be static, and opening the door to local enums and local interfaces.

### 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; }
    ...
}
```
can be migrated to the equivalent, and considerably more readable, record declaration:

```
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(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:

```
    @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. 

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. 

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.

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](#Reflection-API) section below.

### 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
```

### Class-file representation

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

```
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.

### Reflection API

The following public methods will be added to `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.

The method `isRecord` returns true if the given class was declared as a record. (Compare with `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.


Dependencies
--------

In addition to the combination of records and sealed types mentioned above, records lend themselves naturally to [pattern matching][patterns].  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.

[patterns]: https://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html
[sealed]: https://openjdk.java.net/jeps/360
[adts]: https://en.wikipedia.org/wiki/Algebraic_data_type
Comments
[~mr] the motivation was splited as you requested. Thanks
28-04-2020

Please revise this to follow the JEP template (https://openjdk.java.net/jeps/2), in particular to split the Motivation from the Goals.
24-04-2020