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