Finalizing in JDK 16 - Records

Gavin Bierman gavin.bierman at oracle.com
Mon Jul 27 10:54:45 UTC 2020


[Second email of two, looking to close out features we hope to finalize in JDK
16.]


Records
-------

Record classes are a special kind of class that are used primarily to define a
simple aggregate of values. 

Records can be thought of as _nominal tuples_; their declaration commits to a
description of their state and given that their representation, as well as all
of the interesting protocols an object might expose -- construction, property
access, equality, etc -- are derived from that state description.

Because we can derive everything from a common state description, the
declaration can be extremely parsimonious. Here is an example of a record class
declaration: 

    record Point(int x, int y){}

The state or, more formally, a record component list, (int x, int y), drives the
implicit declaration of a number of members of the Point class.

- A `private` field is declared for each record component 
- A `public` accessor method is declared for each record component
- A constructor is declared with an argument list matching the record component
list, and whose body assigns the fields with the corresponding argument. This
constructor is called the _canonical constructor_.
- Implementations of the methods: equals, toString and HashCode. 

The body of a record class declaration is often empty, but it can contain method
declarations as usual. Indeed, if it is necessary, the implicitly declared
members - the accessors, canonical constructor, and equals, toString, or
HashCode methods -- can alternatively be explicitly declared in the body.

Often the reason for explicitly providing a canonical constructor for a record
class is to validate and/or normalize the argument values. To
enhance the readability of record class declarations, we provide a new compact
form of canonical constructor declaration, where only this
validation/normalization code is required. Here is an example:

    record Rational(int num, int denom) { 
        Rational {
            int gcd = gcd(num, denom);
            num /= gcd;
            denom /= gcd;
        }
    }

The intention of a compact constructor declaration is that only validation
and/or normalization code need be given in the constructor body; the remaining
initialization code is automatically supplied by the compiler. The formal
argument list is not required in a compact constructor declaration as it is
taken from the record component list. In other words, this declaration is
equivalent to the following one that uses the conventional constructor form:

    record Rational(int num, int denom) { 
        Rational(int num, int demon) {
            // Validation/Normalization
            int gcd = gcd(num, denom);
            num /= gcd;
            denom /= gcd;
            // Initialization
            this.num = num;
            this.denom = denom;
        }
    }

Once we settled on the design of record classes, things have been pretty stable.
Three issues that did arise were:

1. Initially canonical constructors were required to be public. This was changed
in the second preview. Now, if the canonical constructor is implicitly declared
then its access modifier is the same as the record class. If it is explicitly
declared then its access modifier must provide at least as much access as the
record class.

2. We have extended the meaning of the `@Override` annotation to include the
case that the annotated method is an explicitly declared accessor method for a
record component.

3. To enforce the intended use of compact constructors, we made it a
compile-time error to assign to any of the instance fields in the constructor
body. 

One area that has generated a number of questions is annotations. Our intention
is that an annotation on a record component is propagated to the field,
accessor, and/or constructor parameter, according to the applicability of the
annotation. It is not clear what other design choices there are. So we hope this
is just something that has to be learnt, and afterwards it feels natural.

The records JEP also allows for local record declarations. This is important as
records will often be used as containers for intermediate data within method
bodies. Being able to declare these record classes locally is essential to stop
proliferation of classes. We are aware of some small tweaks that will be
required to the specification during the second preview period, but overall this
feature has not generated any controversy. 



More information about the amber-spec-experts mailing list