Data Oriented Programming, Beyond Records

Brian Goetz brian.goetz at oracle.com
Tue Jan 20 15:28:14 UTC 2026



>         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_.

If a concrete class (including records) have a state description, then 
equality means "all the components are equal".  The different between 
carrier classes and records is the plumbing between the representation 
and API; one can be automated, the other might require manual assistance 
to complete.

> As far as i understand, the carrier class syntax
>   class Point(int x, int y) { ... }
> means that
> - if it's not a concrete class, the implementation should provide an 
> implementation for the canonical constructor and accessors,
> - if it's an interface or an abstract class, the accessors are 
> generated as abstract methods.
>
> Then declaring a field as "component" generates the canonical 
> constructor, the corresponding accessor and toString/equals/hashCode.

This isn't quite right.

  - The state description implies the existence of specific API 
elements: canonical constructor (for constructible entities) and accessors.
  - Deconstruction is derived from the accessors.
  - Since the state description is complete, the default 
equals/hashCode/toString is again derived from the accessors (for 
concrete entities).
  - If the user provides the required API elements, nothing else is 
"generated".

If the required API elements are not present, and they _can_ be derived, 
they are.  This is always true for records.  For accessors, this is true 
when the component is backed by a `component` field, otherwise the 
compiler will force you to explicitly declare it.  For constructors, if 
_no_ constructor is specified, an empty compact constructor will be 
derived.  Further, for a compact constructor, if a field is a component 
field, the component parameter will be implicitly committed to the field 
at the end of the constructor.

So the role of `component` fields is limited to two things: if there is 
no accessor, you get one, and the compact constructor will implicitly 
commit the field for you.  A record is a carrier class which has 
component fields for all components.

> As i said earlier, I think that providing an implementation for 
> toString/equals/hashCode is not a good idea.

What this implies is that you you don't buy the (quite fundamental!) 
notion that a record is a degenerate case of a carrier.  Currently, a 
record is equivalent to a carrier class that (a) extends Record, (b) is 
final, and (c) has private final component fields for each component.  
If you want to "separate" the default semantics of the Object methods, 
you have to separate the notion of carrier from records.  Is that what 
you're suggesting?

> Given that the class can be mutable, generating equals and hashCode 
> hide important details that are

Please stop saying "generate".  (Actually, please stop *thinking* it.)  
That's Lombok-think.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-spec-experts/attachments/20260120/923e9063/attachment-0001.htm>


More information about the amber-spec-experts mailing list