Data Oriented Programming, Beyond Records

Brian Goetz brian.goetz at oracle.com
Wed Jan 21 18:01:52 UTC 2026



> The other solution seems to allow a carrier class to not be restricted 
> to only describe data so like a record equals/hashCode/toString are 
> derived from the fields that define a component.

I'd like to stay away from the "macro generator" view of the world, and 
instead talk about semantics and conceptual models.  The more we 
describe things in terms of macro generation, the less users will be 
able to _see_ the underlying concepts.

I believe I had already anticipated and preemptively dismissed the model 
you outline below, but I'm sure it was easy to miss.  But here's the 
problem: `component` fields are supposed to be implementation details of 
_how_ a carrier class achieves the class contract.  Having the semantics 
of equality depend on which fields are component fields has several big 
problems:

  - It means that clients can't reason about what equality means by 
looking at the class declaration.

  - It means that the behavior of a class will change visibly under 
harmless-looking implementation-only refactorings, such as migrating a 
field from a component field to some other representation.

  - Having flung open the door labeled "macro generator", users will 
instantly demand "I want this to be a component field to get the 
accessor and constructor boilerplate, but I don't want it part of 
equals, can I please have a modifier to condition what counts in equals 
and hashCode, #kthxbye".  This violates Rule Number One of the record 
design, which is "no generation knobs, ever".  Once you have one, you 
are irrevocably on the road to Lombok.

So there are two stabled, principled alternatives:

  - Just don't ever try to derive equals and hashCode
  - Derive equals and hashCode similarly as for records

And of course, the first means that records cannot be considered special 
cases of carriers.  So the latter seems a forced move.

(Meta observation: it is easy, when you have your Code Generator hat on, 
to suggest reasonable-seeming things that turn out to be semantically 
questionable or surprising or inconsistent; this is why we try so hard 
to approach this semantics-first.)

> The carrier class feature:
> A carrier class is a class that define "virtual" components
> - the accessors must be implemented or declared abstract
> - the canonical constructor must be implemented if the class is concrete
> - the record pattern works using accessors.
>
> A carrier class is not restricted to be a data class (so no 
> equals/hashCode/toString), it can represent any class that can be 
> constructed/deconstructed as several virtual components.
>
> The component field feature:
> A field can be declared as "component", i.e. implementing one of the 
> virtual component
> - the field is automatically strict
> - the accessor corresponding to the field can be derived (if not 
> @Override)
> - if at least one of the field is declared as component, 
> equals/hashCode/toString can be derived
> - if at least one of the field is declared as component, the compact 
> constructor syntax is available and inside it, all the fields marked 
> as component are initialized automatically
> - if all virtual components have an associated component field, the 
> canonical constructor can be derived
>
> regards,
> Rémi
>
>
>     On 1/20/2026 1:35 PM, forax at univ-mlv.fr wrote:
>
>
>
>         ------------------------------------------------------------------------
>
>             *From: *"Brian Goetz" <brian.goetz at oracle.com>
>             *To: *"Remi Forax" <forax at univ-mlv.fr>
>             *Cc: *"Viktor Klang" <viktor.klang at oracle.com>,
>             "amber-spec-experts" <amber-spec-experts at openjdk.java.net>
>             *Sent: *Tuesday, January 20, 2026 4:28:14 PM
>             *Subject: *Re: Data Oriented Programming, Beyond Records
>
>
>
>                         The modifier "component" is too close to the
>                         "property" modifier I wanted to include years
>                         ago, it's just to sugary for its own good.
>
>
>                     You know the rule; mention syntax and you forfeit
>                     the right to more substantial comments....
>
>
>                 I'm talking about the semantics, especially the fact
>                 that equals/hashCode and toString() are derived from.
>
>
>             Except that equals/hashCode have nothing to do with the
>             "component" modifier _at all_.  They are derived from the
>             _state description_, in terms of the _accessors_, whose
>             existence is implied directly by the _state description_. 
>
>
>         I do not think you can do that, because it means that a record
>         is not a carrier class.
>
>         Do you agree that equals() and hashCode() of a record are not
>         derived from the accessors ?
>
>         regards,
>         Rémi
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-spec-experts/attachments/20260121/bec538dc/attachment-0001.htm>


More information about the amber-spec-experts mailing list