Bare/method pattern bindings
Gavin Bierman
gavin.bierman at oracle.com
Mon Jul 28 08:43:36 UTC 2025
Thanks for your email. An instanceof statement/pattern-let statement is firmly on our roadmap (i.e. the first solution you suggest).
Gavin
On 28 Jul 2025, at 09:08, Aaryn Tonita <atonita at proton.me> wrote:
In my organization we have settled upon an architecture that leverages sealed interfaces with records implementing those interfaces to build up our domain model. The sealed interfaces enable the needed level of polymorphism and the immutable records with canonical constructors ensure that our business invariants are clear and easily enforced. When transitioning between various states (the implementing records) we can easily switch on the instance of a sealed interface and pattern match the records to easily build the next state. We also similarly use the design when mapping to DTO's or database entities.
A benefit of the pattern matching approach here arises on iteration of the design. As we add features to the application, we will add additional fields to the records and the deconstruction sites need to be updated with the new field. Unlike with JavaBeans, it doesn't happen that we fail to update such a state transition or mapping and create a bug by forgetting to carryover the new field: you cannot compile the code until you add the field to the record deconstruction. It can happen that the field gets marked as ignored (with the _ pattern) generally with a comment.
A nuisance arises however when you want to deconstruct an instance of a record instead of a instance of a sealed interface because patterns are only available in case arms and with instanceof. The nuisance means that you end up with code such as:
public class PatternDeconstruction {
record Foo() {}
record Bar() {}
record FooBar(Foo foo, Bar bar) {}
record OtherFooBar(Foo foo, Bar bar) {}
interface FooBarSink {
void sink(Foo foo);
void sink(Bar bar);
}
OtherFooBar map(FooBar fooBar) {
Objects.requireNonNull(fooBar);
if (fooBar instanceof FooBar(var foo, var bar)) {
return new OtherFooBar(foo, bar);
} else {
throw new AssertionError("Unreachable.");
}
}
OtherFooBar map2(FooBar fooBar) {
return switch (fooBar) {
case FooBar(var foo, var bar) -> new OtherFooBar(foo, bar);
};
}
void sink(FooBarSink sink, FooBar fooBar) {
Objects.requireNonNull(fooBar);
if (fooBar instanceof FooBar(var foo, var bar)) {
sink.sink(foo);
sink.sink(bar);
}
}
}
A choice must be made between giving up on the deconstruction or a choice between an if or a switch. The if with instanceof is especially ugly because of the "unreachable" (null guarding) branch. This doesn't occur on a sink type method, it just confusingly looks like the branch might not always trigger. The switch instead often creates two levels of indentation (not shown here). However, in all cases static analysers complain about this code because it is never conditional.
I was hoping there would be JEP for a bare pattern finding or a method parameter pattern binding, something that would allow one of
OtherFooBar map3(FooBar fooBar) {
FooBar(var foo, var bar) = fooBar;
return new OtherFooBar(foo, bar);
}
OtherFooBar map4(FooBar(var foo, var bar)) {
return new OtherFooBar(foo, bar);
}
but such a JEP doesn't seem to exist. It feels natural to me that such a feature should be present but I am unsure if my intuition is coming from my pattern matching experience in other languages or if any Java developer would naturally share the intuition. In any case, I am hoping you have such a feature on the radar, but I am aware of the derived record creation and would certainly vote for prioritizing that just to give you some anecdata on the practical impacts of the workarounds between the two. We are eagerly awaiting the derived record creation.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20250728/3118196b/attachment.htm>
More information about the amber-dev
mailing list