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