Fair point that we should be more precise about this. For the record,
here is how record equality currently works:
BRIEF HISTORICAL DIGRESSION
The `equals` and `hashCode` implementations are indeed derived from the
fields, not the accessors. To super-simplify the discussion (not to
reopen it):
- 99.9% of the time, the user will not provide explicit accessors, and
in these cases, it makes no difference.
- When a user does provide an explicit accessor, it will almost always
be to perform a defensive copy.
- If the thing being copied is a collection, the answer makes no
difference (Collection::equals is contents-based) and using the accessor
is much^2 more expensive
- If the thing being copied is an array, comparing the copy would
use spurious identity and would be semnatically wrong -- so in this case
you _always_ have to override equals anyway, so in this case it makes no
difference
While this may seem like it is just guessing what users will do, this is
actually rooted in the spec of Record::equals:
Indicates whether some other object is "equal to" this one. In
addition to the general contract of Object.equals, record classes must
further obey the invariant that when a record instance is "copied" by
passing the result of the record component accessor methods to the
canonical constructor, as follows:
R copy = new R(r.c1(), r.c2(), ..., r.cn());
So summarizing the past decision: it should never make a difference
semantically whether we use the fields or the accessors. Using the
accessors is more formally correct, but in some cases, doing so would be
dramatically more expensive without any benefit to the user. So it
seemed an acceptably pragmatic choice to use the fields rather than
accessors.
END DIGRESSION
Now, the question we should be discussing is: how should this
implementation reality map to the goal of "records are degenerate
carriers"?
(It is hard to tell what your intent is here, whether you are merely
trying to capture the details or cast doubt on the stated goal.
Obviously something makes you uncomfortable, I wish we could try to
identify and understand that before suggesting random changes to the
mechanics. Let's see if we can do that.)
On 1/20/2026 1:35 PM, [email protected] wrote:
------------------------------------------------------------------------
*From: *"Brian Goetz" <[email protected]>
*To: *"Remi Forax" <[email protected]>
*Cc: *"Viktor Klang" <[email protected]>,
"amber-spec-experts" <[email protected]>
*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