Feedback for records: Accessors name() vs. getName()
Brian Goetz
brian.goetz at oracle.com
Wed Aug 5 13:41:15 UTC 2020
Thanks for the feedback. Some comments inline.
The good news is that most of your concerns are transient; frameworks
(and there are only a few of them) will have to update to recognize the
new conventions (and it's easy for them to do so.) We expect, within a
year of or so of records coming out, that most frameworks will have
figured this out. One of the reasons to do preview features is so that
frameworks can see these things coming; I expect, for example, that
Spring is likely to be record-ready by the time records are a permanent
feature of the language.
> 1. java.beans.Introspector not recognizing accessors
This one was raised on this list before; there's no reason Introspector
can't learn about records -- all the reflective machinery is there to do
so. Someone at one point offered to contribute this change, but so far
I haven't seen it. We would be happy to take back such a patch.
> I was able to find this email (
> https://mail.openjdk.java.net/pipermail/amber-dev/2020-January/005479.html)
> regarding the same issue but the only thing I could see was a proposal to
> shoehorn Introspector to recognize records instead of making records
> standards-compliant and not having changing anything in the APIs above
> java.lang.
Most of the arguments about accessor naming seem to stem from the same
bit of wishful thinking -- that the getX/setX (and don't forget about
isX) convention is somehow some sort of "standard" (if it were, it would
be a terrible one -- we tend to forget how much pain frameworks go
through to try to recover design intent from naming conventions, because
most of this code was written 22 years ago.) But, this is simply not the
case; it is merely a convention. As language stewards, we have to
balance what is good for the short- and long-term benefit of the
ecosystem. We surely did consider whether it was worth adopting this
convention for the sake of short-term migration, but in the end, we
decided that would be "doing the wrong thing" for the long term.
As I said, most of these have a relatively short-term solution -- we
just have to remind maintainers of frameworks that the language has
evolved, and that they need to do some work to evolve with it. And it's
not a lot of work.
> 2. Unified expression language (EL) in JSP, JSF
> --------------------------------------------------------------
> Web expression language will look quite weird as well (address() method
> versus customer and city properties):
Again, there's no reason why EL can't treat record component accessors
the same way as methods who happen to be named g-e-t-something. And
there's no reason why the () would be needed; it's just that they're
not caught up with the language yet.
We encourage you to request that the maintainers of EL add this to their
bag of magic tricks; it's not hard.
> 3. Spring Framework hacks, Spring (SpEL)
Same story. I'll bet, though, that Spring is already on top of this.
> 3. Groovy inconsistency
> -----------------------
> You can access getName() getters in Groovy using short syntax:
Same.
> 4. Kotlin inconsistency
> -----------------------
> The same as in Groovy also applies to Kotlin:
Same, and again, I'll bet they're already on top of it.
> 5. All libraries will have to be adapted
> ----------------------------------------
> There are a lot of libraries that depend on the get/set convention and you
> would prevent their usage for records. Such as BeanTableModel (swing
> TableModel implementation), Jackson JSON parser and many others.
Yes, there will be some cost for the ecosystem to adapt -- but I think
its less than you're making it out to be -- and less about the naming
that you might think. There are plenty of these libraries, but it's not
thousands -- I'd bet a few tens would probably cover 97% of uses. And
the cost for any individual library is literally a few hours work (its
actually less code to find that a class is a record and that x() is a
component accessor than, say, to determine that setX() and getX() form a
matched pair.) This code is, essentially:
if (clazz.isRecord()) {
Stream.of(clazz.getRecordComponents())
.forEach(c -> addReadOnlyAccessor(clazz,
c.getName(), c.getAccessor()));
}
where `addReadOnlyAccessor` is whatever you do to add the mapping that
"component N of class C is accessed by method M". All these frameworks
work by building a "symbol table" indexed by receiver class and
component name, and map to some description that bottoms out in a
java.lang.Method (which is what RecordComponent::getAccessor returns.)
This is much less work than walking inheritance chains to find methods
that conform to a set of ad-hoc naming conventions, and correlating
similarly-named methods to infer the presence of a synthetic property.
Further, more of the cost here is not about the naming convention, but
the fact that frameworks that reconstruct objects reflectively are used
to calling a no-arg constructor, and then calling setters. That's not an
option with records, regardless of the naming convention chosen for
accessors. So Jackson is going to have to adapt no matter what -- and
the adaptation to immutable objects is far more work than the adaptation
to a new naming scheme (which has reflection support, see above.)
> 6. Impossibility to retrofit existing record-like classes as records
This one is different from the others, as it speaks not to "it will take
a little time for the ecosystem to catch up to the language", but about
what ordinary users have to do. And, this speaks to a design tension we
wrestled with quite a bit.
The tension is, basically, old code vs new. Obviously, if its possible,
we want for a new feature to work great for both old AND new code. But
where there is a conflict between "the way things have been done", and
how they might be done better in the future, there is a tension.
In records, we made the deliberate choice to design the feature for new
code than for catering to the quirks of existing code. (With something
as quirky as the JavaBean naming conventions, we could never be
quirk-for-quirk compatible anyway; consider the getX vs isX issue, which
is inconsistently applied throughout the ecosystem, because, well, it's
a convention, not a standard.) The model for records (immutable,
transparent carriers for a fixed state vector) is different enough from
mutable JavaBeans that, even if we had made this concession to legacy, I
don't think people would have been much happier.
Also, the claim that it is "impossible" to retrofit existing record-like
classes is vastly overstated. If you have a class that meets the
requirements to be a record (representation == construction protocol ==
access protocol == equals/hashCode/toString protocols), you can surely
migrate it to a record, and then, if you have to for legacy reasons,
declare some old-style getters to delegate to the new ones. That is far
from "impossible" -- and was considered in the design (same for having
additional constructors.) I think what you mean is that then you don't
get to write a one-line class declaration? And that's true -- because
you've got a dependency on a framework that uses information from
outside the type system to infer the semantics of your class. So you
pay a few-line "tax" for migrating these classes to records. So yes,
new code will pay less tax than old code, but its not the case you can't
migrate to records and get some benefit. It's just that if you have
extra requirements, you have to write some extra code. (Further, even
this is likely to be transient; once frameworks catch up, the need for
these "bridge" accessors would go away, and you can delete them.)
I realize its easy to jump from "we've been doing it this way for 22
years" to "this is the One True Absolutely Right Way To Do It Forever"
without realizing it, but the language has a responsibility to look
forward as well as backward, and balance the needs of existing code with
the needs of new code. Taking a bad library naming convention and
burning it into the language forever would have been the worse choice.
Cheers,
-Brian
More information about the amber-dev
mailing list