Updated Draft specs for JEP 359 (Records)

Brian Goetz brian.goetz at oracle.com
Sat Nov 2 18:22:51 UTC 2019


>> The basic idea is that with a record, you’ve given up the right to
>> decouple your API from the representation, which is declared in the
>> header.  So you will get a public canonical constructor, accessors,
>> state-based Object methods, and eventually, a deconstruction pattern.
>> You can “override” these methods, but subject to an invariant:
>> “copying” a record (feeding the result of its accessors to its
>> canonical actor) must be equals() to the original.  A record is the
>> state, the whole state, and nothing but the state.
> Sorry, I don't think the current specification of explicitly declared
> canonical constructors achieves this.  As far as I can tell, this is
> legal:
>
> record R(int i) {
>      public R(int i) {
>          this.i = i + 1;
>      }
> }

Yes, the compiler will allow this; the specification alluded to above 
lives in java.lang.Record, the implicit supertype of all record 
classes.  The class you've written above is broken in exactly the same 
way this one is:

     class Foo {
         public boolean equals(Object o) {
             return random.nextBoolean();
         }
     }

Your class is broken -- because it fails to conform to the spec of 
Object::equals -- but it is not the compilers job (nor could it be) to 
catch these sorts of errors.  It's just bad code.  The only way to 
prevent this is to not let people override equals, which would obviously 
be terrible in its own way.

> What is the use case for transformation of record fields in canonical
> constructors?  I would expect canonical constructors to have an
> implicit preamble which assigns all the fields, similar to the compact
> form.  All these constructors can do is checking that the record
> invariants aren't violated.

There are two main reasons why a record implementation might want to 
transform its arguments before committing them to fields: defensive 
copies and argument normalization.  For example, if you have an array 
component, you might well want to clone() it before storing it (and same 
in the accessor); the same may be true with other mutable objects.  
Normalization is less obvious, but still legitimate; you may want to 
clamp values to their bounds, or reduce a rational to lowest terms.  So 
yes, the language lets people write broken records, just as it lets them 
write classes for which equal objects don't have equal hash codes.



More information about the amber-dev mailing list