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