Java 14 record Syntax seems alien
Brian Goetz
brian.goetz at oracle.com
Wed Apr 15 13:35:47 UTC 2020
> I’m sending this email to provide comments on the new Java 14 Record syntax.
> This new feature is very welcome, but the Kotlin syntax
Note that this feature is not unique to (or original with) Kotlin. The
same syntax also shows up in C# and Scala.
> seems alien in
"Alien looking" is a common objection to new features. Every
significantly new feature is likely to have some syntactically new
aspect, and it will be unfamiliar at first. And with nearly every new
feature, the "alien" complaint comes around again. When generics came
along, the outcry was "You're making Java look like C++!" (and this
outcry was loud.) When lambdas came along, it was "this doesn't look
like Java. Can't we use (this syntax that looks familiar to me from my
other favorite language)?"
Fast forward, these features don't look alien to us at all now! They
look like the language we know and love and are comfortable with.
This is not to say the syntax is necessarily _good_ (though I think it
is); it is that the complain of "alien" is inherently a transient one.
It will undoubtedly graduate to "familar" soon enough (though you might
at that point still find it ugly.)
> I’d like to propose a syntax closer to the one for Java interfaces as shown
> below
>
> public record Product{
> String name;
> String description;
> BigDecimal price;
> }
It should probably be no surprise that this suggestion has come up
before, and was discussed and rejected.
One major point against it is that it trades prominent new syntax for a
subtle new (alien!) behavior -- that changing the order of field
declarations is now an incompatible change. Java never worked this way
before, and now, with the small change from "class" to "record", the
field order matters, because it is baked into APIs. That's pretty bad!
Ignoring order of fields, such a syntax would also be brittle, as it
lacks any cues to remind authors that the set of fields is not a mere
implementation detail, but in fact a lightly encrypted public API.
Because a record declaration looks like a constructor argument list,
we're less likely to change it without analyzing the consequences,
because it looks like an API. But the declaration of fields has always
been _just implementation_, and we generally feel much more free to muck
with implementation, and might be surprised to find we've broken our
clients.
Finally, it just doesn't reflect what records are. It might have been
more appropriate to the Billy Boilerplate vision for records, where (as
with Lombok) records are a purely syntactic feature for hiding the
boilerplate under the rug. But that's not what records are. Records
are _nominal tuples_; they _are_ their data. It is entirely appropriate
for the data definition to be front and center in the declaration --
because that's what records are. And, given that constructors (and
eventually deconstructors) are invoked positionally, having the argument
list appear somewhere in the declaration is important.
> This gives power to the developer extend and define the final behavior.
Ultimately, I think what you really wanted was that we selected a
slightly different design center for this feature -- perhaps something
that acted more like a macro processor. And, as we wrote in
http://cr.openjdk.java.net/~briangoetz/amber/datum.html at the beginning
of this project, we knew the central challenge of this feature would be
that everyone has their own slightly different vision for what it should
be.
> 1. Even if all the methods should be overridable, some hints/keywords or
> annotations to not use a field in the generated methods would be a good
> addition.
>
> Because the developer may want to omit a field from the toString because it
> may contain sensitive information or GDPR information, and toStrings are
> usually dumped in the logs as is.
You of course have the option to write these as you did before records.
That said, I think this comment belies the fact that you are viewing
records as largely a syntactic convenience, but that's not what they
are. Records are a _semantic_ feature; they are the state, the whole
state, and nothing but the state. All their protocols -- construction,
deconstruction, equality, hashing, string representation -- are derived
mechanically from a common state description. Routinely mucking with
Object method implementations because not all fields are treated equally
is a hint that what you're working with do not meet the semantic
requirements to be records, and you're only using records for the
syntax. (We considered, briefly, making these methods not overridable,
but unfortunately this was too restrictive (largely because of arrays.)
But it is nearly guaranteed that overriding these will be overused.)
> 2. Some type annotation and some META-INF/“record” config file to set the
> naming strategy of the getter/accessor
> Just for the sake of backwards compatibility with the old naming convention.
>
The set of "code generation knobs" that have been, or could be,
requested for this feature is potentially infinite; this is one of
them. In the end, we settled on a philosophy of "no knobs". Of course,
it is easy to say "but this one knob would solve my problem." But I
don't think it will. Most code that uses the old conventions also do
not meet the requirements for being a record (either because they have
some non-final fields, or some fields without getters, or their
constructor doesn't take all the components, or some components don't
play into equals/hashCode/toString, etc), and so even if you had this
knob, "for migration", it would only push the migration problem a little
bit farther down the road. And then you'd be asking for more knobs.
The reality is: if you're looking for a tool to automate the generation
of JavaBean-style classes for your APIs, this isn't it. In fact, most
APIs probably won't expose very many; we're far more likely to use them
internally as implementation details. And they're great for that!
More information about the amber-dev
mailing list