JDK 14 Preview Records constructors

Remi Forax forax at univ-mlv.fr
Tue Jun 9 20:35:23 UTC 2020


----- Mail original -----
> De: "interlink sg7" <interlink.sg7 at gmail.com>
> À: "amber-spec-comments" <amber-spec-comments at openjdk.java.net>
> Cc: "Jim Laskey" <james.laskey at oracle.com>
> Envoyé: Mardi 9 Juin 2020 00:00:09
> Objet: JDK 14 Preview Records constructors

> Hello,
> 
> 
> 
> I'm not sure if this is the correct place to give feedback on Records, I
> apologise if it isn't.
> 
> I recently played around with the preview Records features from JDK 14 a bit
> (mainly JShell).
> 
> There's a few things I'm at odds with and I'd like to highlight only those
> in this post and I hope you can give me some insights that led to certain
> restrictions
> 
> and where I'm misunderstanding some things maybe.
> 
> My main issue is mostly how different constructors work in Records compared
> to Classes and the resulting inconsistency.
> 
> The following examples are a bit constructed and don't make logical sense,
> but I hope my idea gets across :)

A record provide a kind of transparent API, so the value of the component should be easy to know from calling the constructor,
that why we have restricted the way different constructors of a Record interact with each others.
Your are complaining that your can not obfuscate the computation of a component as you want,
but it's by design.

> 
> 
> 
> 1.	Canonical constructor can not call <custom constructor>, e.g.
> delegating to specialized normalizing constructor with a constant default
> value:
> 
> 
> 
> record Example1(int x) {
> 
>    public Example1(int x) {
> 
>        this(x, 0); // compile error
> 
>    }
> 
> 
> 
>    public Example1(int x, int defaultX) {
> 
>        this.x = x >= 0 ? x : Math.max(defaultX, 0);
> 
>    }
> 
> }


It's a nice example of abuse of constructor, you don't need defaultX to initialize the object, you may need it to compute the value of x, so use a factory method instead of a constructor
  record Example1(int x) {
    public static Example1 create(int x, int default) {
      return new Example1(x >= 0 ? x : Math.max(defaultX, 0));
    }
  }

> 
> 
> 
> 2.	<Custom constructors> must call canonical constructor, e.g. support
> multiple input types or when you can't reuse canonical constructor:
> 
> 
> 
>       record Example2(UUID uuid) {
> 
>           public Example2 {
> 
>               uuid = UUID.nameUUIDFromBytes(uuid.toString().getBytes());
> 
>           }
> 
>    
> 
>            public Example2() { // compile error
> 
>               this.uuid = UUID.randomUUID();
> 
>            }
> 
> 
> 
>            public Example2(String uuid) { // compile error
> 
>               this.uuid = UUID.fromString(uuid);
> 
>            }
> 
>        }

again an example where it's better to use static factory methods (as UUID does) or even better to not provide as many constructors because
'new Example2(UUID.randomUUID())' is easier to understand than 'new Example2()'. Sometimes verbosity wins over having to open the javadoc
to understand what the default constructor does.

> 
> 
> 
> 3.	Canonical Constructor must be public, so it's not possible to have
> only static factories or it forces normalization outside of Records
> constructor:

Yes, right,
it was an issue of the first spec, it's not true any more with Java 15.

 
> 
> 
> 4.	Assignment without <this> in canonical constructor is very
> unnatural, e.g. use of <this> in Classes is very logical when you would
> otherwise reassign the parameter
> 
> In Records however :
> 
> 
> 
>    record Example4(int x) {
> 
>       public Example4 {
> 
>            x = x + 1; // assigns field, but looks nothing <like Java>
> 
>       }
> 
> 
> 
>       public Example4(int x, int plus) {
> 
>            this(x); // hrrng
> 
>            x = x + plus; // reassign parameter
> 
>       }
> 
>    }
> 
> 
> 
>    record Example5(int x) {
> 
>       public Example5 {
> 
>            this.x = x + 1; // read it might be forbidden in the future :(
> 
>       }
> 
> 
> 
>       public Example5(int x, int plus) {
> 
>           this(x); // hrrng
> 
>           this.x = x + plus; // is compile error
> 
>       }
> 
>    }


In a sense you are right, it's not like a constructor of a class but you are in the compact constructor which is not a regular constructor too (parameter are implicit).
A compact constructor do the field assignments for you, so if you want to change the value, you have to change the value of the parameter.
We really hope that people will not change the parameter frequently because again, i will really hate to see the assert in the following code to blow
  var example5 = new Example5(2);
  assert example5.x() == 2
Basically, changing the the value to an equivalent value is fine, not more.

> 
> 
> 
> 5.	It's not possible to define the canonical constructor with the exact
> same parameter list as the Record definition has.
> 
> The simple canonical constructor without parameters should just be an alias on
> for that in my opinion.
> 
> For symmetric reasons (with other constructors) and clarity I would like to
> define the canonical constructor with the required parameters.

If i understand you correctly, you don't like the fact that the compact constructor has not the same rule as a non-compact constructor.
You want the compact constructor to only have the implicit parameters part and not the implicit field assignments part.

There is a bit of theory behind the compact constructor, it's a way to define preconditions [1] in a design by contract way [2],
that why the implicit parameters and implicit field assignments are grouped together.


> 
> 
> 
> I think most of those discrepancies (especially 4) and 5)) come from the
> desire to be less verbose and the definition of records (state and only
> state) and still have some <convenience> like Classes.
> 
> However the limitations and half-way implicit <magic> makes it really
> confusing. I think it would have been better to keep constructors similar to
> the ones from Classes if explicitly defined.
> 
> I mean if the user is already going to manually write constructors (which
> should be a special case) the few lines of assignments don't matter compared
> to the rethinking it needs each time
> 
> with implicits and the restrictions that follow.
> 
> 
> 
> What are your thoughts?
> 
> 
> 
> Kind regards,
> 
> 
> 
> Simon

regards,
Rémi

[1] https://en.wikipedia.org/wiki/Precondition
[2] https://en.wikipedia.org/wiki/Design_by_contract


More information about the amber-dev mailing list