Java 14 records canonical constructor

Johannes Kuhn info at j-kuhn.de
Thu Jun 4 18:24:30 UTC 2020


On 04-Jun-20 20:09, Christian Beikov wrote:
> Why are the arguments mutable? IMO they shoul be final for the 
> canonical constructor. I can alter the example to violate the plain 
> data carrier property:
>
> public record UserRecord(Integer id, String userName) {
>     public UserRecord {
>         if (Character.isUpperCase(userName.charAt(0))) {
>             userName = null;
>         } else {
>             userName = userName.toUpperCase();
>         }
>     }
> }

They need to be mutable to do defensive copies, thereby enforcing deep 
immutability. Consider this:

public record UserRecord(String userName, List<String> mailAddresses) {
     public UserRecord {
         mailAddresses = List.copyOf(mailAddresses);
     }
}

Records would be a lot less useful if this is not possible.

In your example, you break the contract of Record::equals - 
ur.equals(new UserRecord(ur.id(), ur.userName())) would either throw a 
NPE or return false.
It was always possible to break the contract of Object::equals and 
Object::hashCode, which might have surprising effects. This is somewhat 
the same.

- Johannes

>
> I don't know how you are anticipating to implement de-construction 
> pattern matching, but if the reason for records being plain data 
> carriers is because you want to construct records to use them as 
> carriers for the pattern matching, then such a record would fail to be 
> usable with pattern matching, because the canonical constructor fails 
> to create the record based on the components.
>
> Am 04.06.2020 um 20:01 schrieb Brian Goetz:
>> tl;dr: you're right, and we've already fixed it for an upcoming preview.
>>
>> By way of explanation about how this came about ... it took us a 
>> little bit of time to come to the current formulation of "compact 
>> constructor", and the thinking about auto-committing fields predates 
>> the current formulation.  So the behavior you are seeing has its 
>> roots as "more lenient constructors", rather than encouraging a 
>> different construction mode.  It took a while for the details to 
>> catch up with the programming model on this one.  Surely the intent 
>> was as you say -- the constructor should only contain stuff that 
>> can't be derived from the record declaration, which means it will 
>> often be empty.
>>
>> The model we have now is: the constructor arguments show up as set by 
>> the caller, but are mutable -- if you want to normalize them, do so 
>> by mutating them, and then they all get committed to the fields at 
>> once.  The benefit here is that there are fewer weird orderings that 
>> the user has to reason about.
>>
>> This is a great example of why we do preview releases -- since its 
>> hard to imagine all of these up front.
>>
>> On 6/4/2020 1:53 PM, Christian Beikov wrote:
>>> Hello,
>>>
>>> I recently found out that a record constructor can set the record 
>>> components explicitly and was kind of surprised. I thought that a 
>>> record is a plain data carrier and that I can re-construct a record 
>>> object from it's components, but this is not guaranteed.
>>>
>>> Here a quick example:
>>>
>>> public record UserRecord(Integer id, String userName) {
>>>     public UserRecord {
>>>         if (Character.isUpperCase(userName.charAt(0))) {
>>>             this.userName = null;
>>>         } else {
>>>             this.userName = userName.toUpperCase();
>>>         }
>>>     }
>>> }
>>>
>>> IMO setting a field/record component directly should be prohibited. 
>>> A developer should move such a logic to a static method or a 
>>> different constructor. The canonical record constructor should only 
>>> do sanity checks i.e. argument is not-null, not blank etc. but not 
>>> alter the arguments or set the fields manually. It's ok for an 
>>> additional non-canonical constructor to alter the arguments though.
>>>
>>> What do you think? Was that an oversight or is this intended? If it 
>>> is intended, what is the reasoning for allowing this?
>>>
>>> I don't have a problem with this behavior. I just wouldn't have 
>>> expected it. If I remember correctly, there were some talks about 
>>> de-construction patterns at conferences that pointed out, that being 
>>> able to re-construct a record from the component state is a vital 
>>> property of records.
>>>
>>> Regards,
>>>
>>> Christian
>>>
>>



More information about the amber-dev mailing list