Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
JDK-8225052 :
|
|
JDK-8225053 :
|
|
JDK-8225054 :
|
|
JDK-8225055 :
|
|
JDK-8225057 :
|
|
JDK-8225058 :
|
|
JDK-8226314 :
|
|
JDK-8227113 :
|
|
JDK-8233526 :
|
|
JDK-8236439 :
|
|
JDK-8237019 :
|
Summary ------- Enhance the Java programming language with [records][records]. Records provide a compact syntax for declaring classes which are transparent holders for shallowly immutable data. This is a [preview language feature](http://openjdk.java.net/jeps/12) in JDK 14. [records]: http://cr.openjdk.java.net/~briangoetz/amber/datum.html Motivation and Goals -------------------- 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 plain "data carriers" that serve as simple aggregates. To write a data carrier class properly, one has to write a lot of low-value, repetitive, error-prone code: constructors, accessors, `equals()`, `hashCode()`, `toString()`, etc. Developers are sometimes tempted to cut corners such as omitting these important methods (leading to surprising behavior or poor debuggability), or 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 will help _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 simple aggregates 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, clear, and concise to declare shallowly-immutable, well-behaved nominal data aggregates. Non-Goals --------- It is not a goal to declare "war on boilerplate"; in particular, it is not a goal to address the problems of mutable classes using the JavaBean naming conventions. It is not a goal to add features such as properties, metaprogramming, and annotation-driven code generation, even though they are frequently proposed as "solutions" to this problem. Description ----------- _Records_ are a new kind of type declaration in the Java language. Like an `enum`, a `record` is a restricted form of class. It declares its representation, and commits to an API that matches that representation. Records give up a freedom that classes usually enjoy: the ability to decouple API from representation. In return, records gain a significant degree of concision. A record has a name and a state description. The state description declares the _components_ of the record. Optionally, a record has a body. For example: ``` record Point(int x, int y) { } ``` Because records make the semantic claim of being simple, transparent holders for their data, a record acquires many standard members automatically: - A private final field for each component of the state description; - A public read accessor method for each component of the state description, with the same name and type as the component; - A public constructor, whose signature is the same as the state description, which initializes each field from the corresponding argument; - Implementations of `equals` and `hashCode` that say two records are equal if they are of the same type and contain the same state; and - An implementation of `toString` that includes the string representation of all the record components, with their names. In other words, the representation of a record is derived mechanically and completely from the state description, as are the protocols for construction, deconstruction (accessors initially, and deconstruction patterns when we have pattern matching), equality, and display. ### Restrictions on records Records cannot extend any other class, and cannot declare instance fields other than the private final fields which correspond to components of the state description. Any other fields which are declared must be static. These restrictions ensure that the state description alone defines the representation. Records are 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. The components of a record are implicitly final. This restriction embodies an _immutable by default_ policy that is widely applicable for data aggregates. Beyond the restrictions above, records behave like normal classes: they can be declared top level or nested, they can be generic, they can implement interfaces, and they are instantiated via the `new` keyword. The record's body may declare static methods, static fields, static initializers, constructors, instance methods, and nested types. The record, and the individual components in a state description, may be annotated. If a record is nested, then it is implicitly static; this avoids an immediately enclosing instance which would silently add state to the record. ### Explicitly declaring members of a record Any of the members that are automatically derived from the state description can also be declared explicitly. However, carelessly implementing accessors or `equals`/`hashCode` risks undermining the semantic invariants of records. Special consideration is provided for explicitly declaring the canonical constructor (the one whose signature matches the record's state description). The constructor may be declared without a formal parameter list (in this case, it is assumed identical to the state description), and any record fields which are _definitely unassigned_ when the constructor body completes normally are implicitly initialized from their corresponding formal parameters (`this.x = x`) on exit. This allows an explicit canonical constructor to perform only validation and normalization of its parameters, and omit the obvious field initialization. For example: ``` record Range(int lo, int hi) { public Range { if (lo > hi) /* referring here to the implicit constructor parameters */ throw new IllegalArgumentException(String.format("(%d,%d)", lo, hi)); } } ``` ### Grammar ``` RecordDeclaration: {ClassModifier} record TypeIdentifier [TypeParameters] (RecordComponents) [SuperInterfaces] [RecordBody] RecordComponents: {RecordComponent {, RecordComponent}} RecordComponent: {Annotation} UnannType Identifier RecordBody: { {RecordBodyDeclaration} } RecordBodyDeclaration: ClassBodyDeclaration RecordConstructorDeclaration RecordConstructorDeclaration: {Annotation} {ConstructorModifier} [TypeParameters] SimpleTypeName [Throws] ConstructorBody ``` ### Annotations on record components Declaration annotations are permitted on record components if they are applicable to record components, parameters, fields, or methods. Declaration annotations that are applicable to any of these targets are propagated to implicit declarations of any mandated members. Type annotations that modify the types of record components are propagated to the types in implicit declarations of mandated members (e.g., constructor parameters, field declarations, and method declarations). Explicit declarations of mandated members must match the type of the corresponding record component exactly, not including type annotations. ### 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, where `java.lang.reflect.RecordComponent` is a new class. 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 `RecordComponent` in the array, including its name, type, generic type, annotations, and its 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 lighterweight means of expressing some aggregates, the result is often inferior aggregates: - A central aspect of Java's 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 ----------- Records go well with [sealed types (JEP 360)][sealed]; records and sealed types taken together form a construct often referred to as [algebraic data types][adts]. Further, 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
|