JEP 405 Record Patterns and Backwards Compatible Changes
Brian Goetz
brian.goetz at oracle.com
Mon Jun 13 14:37:21 UTC 2022
While not guaranteed, what the OP has observed is that adding new
components at the end can be made *effectively* compatible by adding a
constructor -- and this is something we deliberately considered in the
design. And as the dual of constructors, deconstruction patterns have
the same property. When classes can declare deconstruction patterns,
records will be able to do so too, and not "for compatibility", but
because deconstruction pattern completes a part of the object model.
(As to serialization, the serialization strategy, which is based on
default serialization for the fields but runs through the ctor, is
tolerant to adding fields, as long as their default values are not
rejected by the constructor.)
But I think the whole first paragraph of your reply is mostly a
distraction. I think what you really mean is: what should we recommend
people do in terms of exposing records in APIs? This is the key question:
> So it's not clear to me why someone will want to use a record pattern
if backward compatibility is a concern.
And indeed, the JDK -- the library that cares more about backward
compatibility than any other on earth -- is likely to be conservative
about putting records in public APIs. But, as observed above, you can
go a long way with adding extra ctors and dtors and be mostly compatible
-- and I think its reasonable for libraries to do that too, with their
eyes open of course.
> Brian,
> I'm not convinced that source compatibility with deconstructors is a goal worth pursuing, as you say, it's only in the case where you add new components at the end, those components must have meaningful default values and the record is not serializable. So this is an awfully a very specific corner case.
>
> For me, this is equivalent to the question: when to use a type pattern + accessors and when to use a record pattern ?
>
> In term of backward compatibility, a type pattern + accessors enables better backward compatibility than a record pattern, because you can add new components even in the middle, it will still work. So it's not clear to me why someone will want to use a record pattern if backward compatibility is a concern.
>
> Record patterns, by construction, matches a shape which is fixed and ordered, you only recognize those components of that length in this particular order. That adds a lot of constraints to a class/record you want to be backward compatible.
>
> Perhaps a better road to help with backward compatibility is to have a way to specify at declaration site that using a record pattern on a record/class is disallowed, like you can have a private constructor.
But the canonical constructor of a record cannot be private, and neither
can the canonical deconstructor (because you can always simulate it with
instanceof and accessor calls.)
> And obviously, non-overloadable named pattern methods can be used in case of components that are co-dependents.
>
> Rémi
>
> PS: being able to juggle with several definition of a data piece with optional values, default values, etc. is a real use case, but i think it is better tackled by serializer/deserializer (in the generic sense e.g. jackson/object mapper/grpc-protobuf, etc) libraries than by the record itself.
>
>> On 6/11/2022 8:39 PM, Nathan Walker wrote:
>>> Currently in Java 18 we can make backwards compatible changes to the
>>> contents of a record.
>>>
>>> Given:
>>>
>>> record Point(int x, int y){ }
>>>
>>> We can add point z with:
>>>
>>> record Point(int x, int y, int z) {
>>>
>>> @Deprecated
>>> Point(int x, int y){ this(x, y, 0); }
>>> }
>>>
>>> Or given:
>>>
>>> record Point(int x, int y, int z){ }
>>>
>>> We can remove point z with:
>>>
>>> record Point(int x, int y) {
>>>
>>> @Deprecated
>>> Point(int x, int y, int z){ this(x, y); }
>>>
>>> @Deprecated
>>> public z(){ return 0; }
>>> }
>>>
>>> If they are marked serializable then even the serialized forms maintain
>>> backwards compatibility, with the z value in the first example being
>>> defaulted to 0 during deserialization, and the z in the second example
>>> being ignored during deserialization.
>>>
>>> But if I understood JEP 405 correctly, a record pattern depends on matching
>>> the exact record components. So is there a way in JEP 405 to change a
>>> record's components without breaking everywhere that is using the record in
>>> a pattern match statement? I would be concerned about using records in any
>>> public API if every minor change to a record's structure will require a new
>>> major version release under semantic versioning rules.
>>>
>>> Thanks for your time.
More information about the amber-dev
mailing list