Java 14 records canonical constructor

Christian Beikov christian.beikov at gmail.com
Thu Jun 4 20:18:41 UTC 2020


Am 04.06.2020 um 21:39 schrieb Brian Goetz:
> OK, this seems a totally different question to the one you initially 
> asked :)
>
> You're basically saying: why is a record constructor allowed to do 
> _anything_ other than check invariants?
>
> I understand where this desire comes from.  But, let's pull on that 
> string.
>
> Suppose you want to write a record like:
>
>     record Rational(int num, int denom) { }
>
> It seems pretty reasonable to want to normalize the numerator and 
> denominator to lowest terms; among other things, this makes it easy to 
> obtain the likely-desired invariant of `new Rational(1, 2)` equals 
> `new Rational(2, 4)`.  Sure, you could export that responsibility on 
> the client, or provide a separate factory `Rational::inLowestTerms`, 
> but surely you see how that is more error-prone.

Let's say that normalization is allowed in the canonical constructor, 
how would that interact with e.g. pattern matching which I assume is the 
main consumer of the "re-construction" property of records.

Would the following match the first or the second case?

switch (new Rational(1, 2)) {
     case Rational(2, 4):
         break;
     case Rational(1, 2):
         break;
}

I guess what I am asking is, will the pattern cause a Rational object to 
be instantiated such that the normalization can happen for that pattern 
object as well? If not, then it seems to me that a canonical constructor 
that alters/mutates the arguments is broken and can't be used for 
pattern matching.

> Suppose you have a record whose components are mutable (say, an 
> array.) You might well want to perform a defensive copy on the way in 
> (and, possibly on the way out, in the accessor.)
> You might be willing to say "ban all these uses, they should all be 
> illegal", and that would be a principled and defensible position. But 
> surely you see as well that this would lead to less useful records.
> Now, we struggled a lot with whether we can forbid the "bad" cases and 
> keep the "good" cases.  This went about as well as you might expect.  
> So instead, we settled on allowing users to override accessors, to 
> normalize arguments in the constructor, and to override equals, with 
> the caveat that `java.lang.Record` imposes some stronger invariants on 
> `equals()`, and you'd better maintain those.

I'm not saying that these use cases should be banned, but maybe the 
canonical constructor should be made package-private by default? A user 
can then use the "compact named constructor" or a custom constructor for 
doing defensive copying. That way the state can be kept deeply immutable 
and at the same time the canonical constructor is ensured to be safe.



More information about the amber-dev mailing list