Records -- current status

Stephen Colebourne scolebourne at joda.org
Wed Apr 11 23:46:53 UTC 2018


On 16 March 2018 at 18:55, Brian Goetz <brian.goetz at oracle.com> wrote:
> There are a number of potentially open details on the design for records.
> My inclination is to start with the simplest thing that preserves the
> flexibility and expectations we want, and consider opening up later as
> necessary.

I'd like to agree, but I fear that what is proposed is too
restrictive, and that it will be an attractive nuisance, especially
given the large cliff edge (acknowledged by the docs as being a
problem, but I worry "wait" for the improvements in classes may get
overtaken by other more pressing concerns). The cliff egde is my
biggest concern with records.

> One of the biggest issues, which Kevin raised as a must-address issue, is
> having sufficient support for precondition validation. Without foreclosing
> on the ability to do more later with declarative guards, I think the recent
> construction proposal meets the requirement for lightweight enforcement with
> minimal or no duplication.  I'm hopeful that this bit is "there".

Having to redeclare all fields as constructor parameters when
validating in a primary constructor would be unhelpful boilerplate -
I'm not clear if that is still required.

I still think a keyword like `new` is better than the class name for
the primary constructor given its specialness.

>  - Extension.  The proposal outlines a notion of abstract record

Abstract records have zero appeal to me, though they are not a deal
breaker. I'd be more interested to see if some kind of record
interface was practical.

>  - Concrete records are final.

+1

>  - Additional constructors.  I don't see any reason why additional
> constructors are problematic

+1

>  - Static fields.  Static fields seem harmless.

+1

>  - Additional instance fields.  These are a much bigger concern. While the
> primary arguments against them are of the "slippery slope" variety, I still
> have deep misgivings about supporting unrestricted non-principal instance
> fields, and I also haven't found a reasonable set of restrictions that makes
> this less risky.

This is a big deal for me. We use additional fields all the time.
Those fields are not part of the state. They do not get used in equals
or hashCode.

The field could be hidden, say by a `cached` keyword to any method:

  public cached String name() {
    return firstName + " " + surname;
  }

While there are ways to abuse that, I think it would meet the use
cases I see for additional fields.

>  - Mutability and accessibility.  I'd like to propose an odd choice here,
> which is: fields are final and package (protected for abstract records) by
> default, but finality can be explicitly opted out of (non-final) and
> accessibility can be explicitly widened (public).

That is an odd choice.

IMO, fields should be private with no way to change the scope. There
is a clear way for everyone to access that data - via the getter -
otherwise why bother having getters? If you can guarantee that there
is a public getter, what value could ever be derived from having a
public field?

I also think private fields makes it easier to migrate a record to a
class if you need to, as the class you would migrate to would almost
certainly have private fields.

Fields being final by default with a non-final keyword seems OK,
although perhaps `mutable record` might be a clearer way to say it
(with `final` used to make some things final):

  mutable record Person(String forename, final String surname) {};

I'd also like to argue for package-scoped records. We have quite a
lots of data classes today that are package scoped, and it is a great
way to limit how far the data classes can be seen. Not having
package-scoped records would make me very sad. FWIW, I don't think
this conflicts with records being transparent - I'd expect reflective
access to treat a package scoped record as if it were public.

>  - Accessors.  The obvious naming choice for these accessors is fieldName().

+1

> No provision for write accessors; that's bring-your-own.

A little weird, but I think thats OK. Trouble is that I don't think
you can start generating them (or withers) later - but maybe you have
a trick up your sleeve for that.

>  - Core methods.  Records will get equals, hashCode, and toString.  There's
> a good argument for making equals/hashCode final

Yes, equals/hashCode should be final.

> Records could be safely made cloneable() with automatic support too (like arrays), but not
> clear if this is worth it (its darn useful for arrays, though.)

Meh. Cloneable seems tricky as it always gets into how deep to clone
issues. Leave that to humans.

Some other things:

- Caching of hashCode() is vital for performance in certain cases
where object used as a Map key. Not having this feature would prevent
use of records in use cases where it really makes sense. This feature
could be done for all immutable records, or it could be opt in. This
is a must have for me.

- In syntax terms, defining fields in the class header doesn't match
the design of enums. It also doesn't scale well beyond two fields or
when there are annotations (I'll happily concede the class header
style looks good in unrealistic presentation examples because its so
short). I also think the more remote Javadoc is undesriable, and it
will encourage developers to write shorter, less helpful docs (I claim
that Javadoc for  method parameters is demonstrably shorter than
Javadoc for methods/fields for well written code).

While Scala, Kotlin, and no doubt others have gone down the class
header route, I want to at least ensure there is a proper debate about
whether it is right in Java, taking in evidence of the typical number
of fields expected per record to give some more realistic pieces of
example code.(Previous threads have set an expectation that the number
of fields per record will be low, but I don't see the evidence for
that. Many data classes I see have lots of fields.)

- My experience suggests that if you prevent the data classes in your
system from ever returning null, then you tend to be most of the way
towards eliminating null and NPE in the whole system. To achieve the
equivalent of the code we have today with no nulls would mean having
to write a primary constructor that manually validates every field on
every. single. record. This is unhelpful boilerplate. I'm open to
options as to how this could be tackled, but always manually writing a
primary constructors seems like the wrong answer. (If we're flipping
to final-by-default, why not non-null-by-default...)

- Records add the ability to convert data in and out in a standard way
by a standard field name, exactly as needed by serialization
frameworks and similar. However, there is currently no library
proposal to provide an abstraction across records, JavaBeans and other
classes representing data. I think it would be a mistake to introduce
records without also introducing a library providing that abstraction
into the java.base module.

thanks
Stephen


More information about the amber-dev mailing list