Java 14 records canonical constructor
Kevin Bourrillion
kevinb at google.com
Thu Jun 4 18:43:03 UTC 2020
(Pedantic guy speaks: we should say *reassigning* the parameter variables,
not *mutating* them, which is a different thing we could do if the
parameters are of mutable types.)
There's nothing wrong with idempotently transforming record data to a
canonical form on its way in.
On Thu, Jun 4, 2020 at 11:27 AM Johannes Kuhn <info at j-kuhn.de> wrote:
> 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
> >>>
> >>
>
>
--
Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com
More information about the amber-dev
mailing list