Record construction
Brian Goetz
brian.goetz at oracle.com
Wed Mar 14 14:55:54 UTC 2018
Let me summarize what we're proposing for record constructors. I think
we can simplify it so that it is easy to do parameter validation and
normalization without falling off the syntactic cliff, and with fewer
new features. (As a bonus, I think these ideas mostly generalize to
more concise non-record constructors too, but I'm going to leave that
for another day -- but I'll just note that one of our goals for records
is for them to be "just macros" for a set of finer-grained features, so
that even if a class doesn't meet the requirements to be a record, it
can still benefit from more concise construction, or equals/hashCode, or
whatever.)
Goals
-----
it should be easy to add constructor parameter validation, like
Preconditions.require(x > 0);
without significant repetition of record elements. Similarly, it should
be easy to normalize arguments before they are committed to fields:
if (name == null)
name = "";
again without significant repetition. If there are ancillary fields, it
should be easy to initialize them in the constructor body without
bringing back any boilerplate.
Ideally, these mechanisms are consistent with construction idioms for
non-record classes (records and classes should not be semantically
different).
Record Constructors
-------------------
A record class:
record Point(int x, int y) { }
has a "class signature" `(int x, int y)`. Record classes always have a
_default constructor_ whose signature matches the class signature; these
can be implicit or explicit.
The constructor syntax
record Point(int x, int y) {
Point {
}
}
is proposed as a shorthand for an explicit default constructor:
record Point(int x, int y) {
Point(int x, int y) {
}
}
The arguments A0..An are divided into super-arguments A0..Ak and
this-arguments Ak+1..An. The default super invocation is super(A0..Ak).
If a default constructor does not contain an explicit super-call, an
implicit super constructor call is provided at the start of the
constructor, just as we do now with implicit no-arg super-constructors.
An implicit field initialization, consisting of `this.xi = xi` for i in
k+1..n, is added to the _end_ of the constructor.
This makes both validation and normalization work; if the constructor
body contains only:
Preconditions.require(x > 0);
(this is just a library call), the code is executed after the
super-call, and `x` refers to the arguments. Just like in constructors
today. Similarly, normalization:
if (name == null)
name = "";
is executed after the super-call, but before the field initialization,
so any the update to the parameter "sticks", and `name` refers to the
argument, not the field, just as today. So existing idioms work, just
by removing the boilerplate.
We can protect against double-initialization either by (a) not allowing
the user to explicitly initialize fields corresponding to Ak+1..An, or
(b) if the corresponding field is definitely initialized by the explicit
constructor body, leave it out of the implicit initialization code.
If users want to adjust what gets passed to the super constructor, just
use an explicit super-call. If users want to adjust what gets written
to the field, overwrite the parameter before it is written to the
field. Both of these are consistent with how constructors work today.
If the record has extra fields (if allowed), the constructor can just
initialize them:
Point {
norm = Math.sqrt(x*x + y*y);
}
Again, `x` and `y` here refer to constructor arguments, and everything
works, with no repetition.
Summary:
- The required idioms work, just leaving out the boilerplate
- Very similar to existing constructors
- No need to support statements before super (though we can add this
later, if we see fit)
- No need for `default.this(...)` idiom *at all*
(pause for agreement)
More information about the amber-spec-experts
mailing list