Fwd: JDK 14 Preview Records constructors

Brian Goetz brian.goetz at oracle.com
Tue Jun 9 21:17:49 UTC 2020


Yes, the error message should be more specific. "Another constructor of 
class Foo" would do it.

On 6/9/2020 5:08 PM, Johannes Kuhn wrote:
> Thanks for sharing this.
>
> Highlighted a few things I was not aware of as EJC doesn't check for 
> that.
> Filled a bug on the eclipse bug tracker[1].
>
> Playing around with that, I found the following "inconsistency":
>
>     public record Foo(int bar) {
>         public Foo() {
>             super();
>             this.bar = 0;
>         }
>     }
>
> Fails to compile with "constructor is not canonical, so its first 
> statement must invoke another constructor".
> I wonder what "super();" is? (According to JLS § 8.8.7.1, it's an 
> Explicit Constructor Invocation)
>
> Should probably display a different, more specific error message.
> Like "constructor is not canonical, so its first statement must invoke 
> another constructor of this record".
>
> (Also, it *might* be useful to have a non-public non-canonical 
> constructor that doesn't copy an array. But that's a different 
> discussion.)
>
> - Johannes
>
> [1]: https://bugs.eclipse.org/bugs/show_bug.cgi?id=564146
>
> On 09-Jun-20 21:24, Brian Goetz wrote:
>> Received on the spec-comments list.
>>
>>
>>
>>
>> -------- Forwarded Message --------
>> Subject:     JDK 14 Preview Records constructors
>> Date:     Tue, 9 Jun 2020 00:00:09 +0200
>> From:     interlink.sg7 at gmail.com
>> To:     amber-spec-comments at openjdk.java.net
>> CC:     james.laskey at oracle.com
>>
>>
>>
>> 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 :)
>>
>>
>> 1. Canonical constructor can not call <custom constructor>, e.g.
>> delegating to spezialized 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);
>>
>> }
>>
>> }
>>
>>
>> 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);
>>
>> }
>>
>> }
>>
>>
>>
>> 3. Canonical Constructor must be public, so it's not possible to have
>> only static factories or it forces normalization outside of Records
>> constructor:
>>
>>
>> record Example3(UUID uuid) {
>>
>>
>> private Example3 { // compile error
>>
>> }
>>
>>
>> public Example3(String uuid) { // compile error
>>
>> String uuidNormalized = StringUtils.toLowerCase(uuid);
>>
>> try {
>>
>> this.uuid = UUID.fromString(uuidNormalized);
>>
>> catch(IllegalArgumentException e) {
>>
>> this.uuid = null;
>>
>> }
>>
>> }
>>
>>
>> public static Example3 fromString(String uuid) {
>>
>> return new Example3(uuid);
>>
>> }
>>
>> }
>>
>>
>> 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
>>
>> }
>>
>> }
>>
>>
>> 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
>> for that in my opinion.
>>
>> For symmetrie reasons (with other constructors) and clearity I would 
>> like to
>> define the canonical constructor with the required parameters.
>>
>>
>> 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
>>
>>
>>
>>
>



More information about the amber-dev mailing list