Feedback on records (JEP 359)

Anthony Vanelverdinghe anthonyv.be at outlook.com
Sat Apr 25 15:02:44 UTC 2020


Hi

The following is feedback on my initial experience with records:

1) a request: besides Brian's datum write-up [1], it would be useful to 
have a more practice-oriented document, similar to the one for text 
blocks [2]. For example, one thing I'd like such document to expand on, 
would be migration compatibility between records and classes (i.e. if I 
expose a record in my public API, how will that impact my ability for 
evolving it, e.g. converting it into a class?). This would ideally also 
include a FAQ which consolidates the wisdom from the mailing lists (e.g. 
"Why is the canonical constructor specified to be public?" [3], "Why 
aren't accessor methods generated as final?" [4], etc.).

2) a proposal: a method `Stream<IndexedElement<T>> indexed()` on Stream, 
which wraps each Stream element in a `record IndexedElement<T>(long 
index, T t)`
This would allow for a natural, concise way to access indices (cf. e.g. 
[5]).

3) a proposal: if a non-canonical constructor is defined, it should also 
be required to define the canonical constructor
This is a trivial one-liner to add, and it reminds people that the 
canonical constructor is:
a) present (unlike classes, where, if you define a constructor yourself, 
the implicit constructor doesn't exist)
b) public (if there's a single private constructor, I regularly forget 
that there's still a public constructor as well)

4) an observation: I noticed that the ease with which records can be 
created, might lead to recorditis, where e.g. you start out with:

record Episode(int season, int number, String title) {}

and you end with:

record Season(Id id, EpisodeCount numEpisodes) {
     record Id(int number)
     record EpisodeCount(int numEpisodes) {}
}
record Episode(Id id, Title title) {
     record Id(Season.Id season, int number) {}
     record Title(String title) {}
}

which brings me back to my first point: I'm hoping that a document with 
best practices and advice would give me some guidance on when to stop.

5) an observation/proposal: I need to remind myself sometimes that 
record components are nullable. Would it be an option to introduce a 
`nullable-record` keyword, where `record` would not allow any of its 
components to be null, whereas `nullable-record` would?

6) a proposal: there's a recurring occurrence of boilerplate in my 
records, which could be eliminated by introducing a `sorted-record` 
keyword. This would work as follows:

sorted-record YearMonth(Year year, Month month) {}

is equivalent to:

record YearMonth(Year year, Month month) implements Comparable<YearMonth> {

     private static final Comparator<YearMonth> NATURAL_ORDER = Comparator
         .comparing(YearMonth::year, 
Comparator.nullsLast(Comparator.naturalOrder()))
         .comparing(YearMonth::month, 
Comparator.nullsLast(Comparator.naturalOrder()));

     @Override
     public int compareTo(YearMonth o) {
         return NATURAL_ORDER.compare(this, o);
     }

}

on the condition that each record component either implements 
Comparable, or is an array type whose component type implements Comparable.
Analog to YearMonth (yes, I'm aware that YearMonth et al. are already in 
the JDK ;)), there exist many examples, where a natural order on the 
instances is achieved by comparing all components, in order, from left 
to right. For example `Season.Id` and `Episode.Id` given above, 
`Type(String packageName, String typeName)`, `Error(String file, int 
line, int column)`, `Person(String lastName, String firstName)`, ...
This is similar to how Enum also implements Comparable according to what 
one would naturally expect.
This is tedious to write, but seems relatively easy for a compiler to 
generate.
This would also nudge developers to declare record components in a 
consistent fashion. For example, in Europe, `LocalDate(int day, int 
month, int year)` would be pretty natural, but everyone would write 
`sorted-record LocalDate(int year, int month, int day)`.
W.r.t. naming, "sorted-record" would fit naturally with 
SortedMap/SortedSet, Stream::sorted, etc.
And the use of a hypenated keyword would allow `sorted-class` as well 
(though in my experience it's much less common for a class to only 
contain instance variables that implement Comparable).
There could also be an Xlint warning for a sorted-record that contains 
components which have a type whose Comparable implementation is known 
not to be compatible with Object::equals (though this would only be 
verifiable for JDK classes, with BigDecimal as the notable example).

Thanks in advance for any feedback.

Kind regards,
Anthony

[1] https://cr.openjdk.java.net/~briangoetz/amber/datum.html
[2] https://cr.openjdk.java.net/~jlaskey/Strings/TextBlocksGuide_v10.html
[3] 
https://mail.openjdk.java.net/pipermail/amber-spec-observers/2019-November/001883.html
[4] https://mail.openjdk.java.net/pipermail/amber-dev/2020-April/005888.html
[5] 
https://stackoverflow.com/questions/18552005/is-there-a-concise-way-to-iterate-over-a-stream-with-indices-in-java-8



More information about the amber-dev mailing list