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

Reply via email to