From interlink.sg7 at gmail.com Mon Jun 8 22:00:09 2020 From: interlink.sg7 at gmail.com (interlink.sg7 at gmail.com) Date: Tue, 9 Jun 2020 00:00:09 +0200 Subject: JDK 14 Preview Records constructors Message-ID: <00b801d63de0$2da00a00$88e01e00$@gmail.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 , 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. 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 in canonical constructor is very unnatural, e.g. use of 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 } 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 like Classes. However the limitations and half-way implicit 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