Java sealed courses and exhaustive sample matching – Neil Madden

Java 17 launched sealed classes, which let you explicitly checklist the allowed sub-types of an interface or base class. For instance, right here’s a toy instance utilizing a sealed interface and information (internal courses are implicitly added to the permitted sub-types if an specific checklist isn’t given):
public sealed interface SealedType {
document TypeA() implements SealedType {}
document TypeB() implements SealedType {}
static SealedType of(String sort) {
return swap (sort) {
case “A” -> new TypeA();
case “B” -> new TypeB();
default -> throw new IllegalArgumentException();
};
}
}
If you’re accustomed to useful programming languages with algebraic datatypes, you possibly can view this as just like a datatype declaration in Haskell or ML:
knowledge SealedType = TypeA | TypeB
We can then use this in a easy Main class:
void most important(String[] args) {
var val = SealedType.of(args[0]);
System.out.println(swap (val) {
case SealedType.TypeA() -> “A”;
case SealedType.TypeB() -> “B”;
});
}
OK, not so thrilling. But one factor to notice right here is that we didn’t have so as to add a default clause to the swap expression in our most important technique. This is as a result of sealed courses (and enums) allow exhaustiveness checking: the compiler is aware of precisely what the potential instances are, and so can examine when you’ve got lined all of them. If you could have, then you definately don’t want a default clause. If you overlook one (and don’t have a default clause), then you definately get a compile-time error.
This is nice once you wish to be sure that all makes use of of some sort do cowl all the instances, but it surely does introduce a brand new sort of breaking change: including a brand new sub-type to a sealed class/interface could break shoppers of that code. For instance, including a brand new TypeC case to our instance will trigger the principle technique to fail to compile as a result of lacking case. So for those who export a sealed sort in your API then including a brand new subtype is a breaking change that will require a serious model bump (for those who’re following SemVer).
Compile-time or runtime error?
Although Java will produce a compile-time error for a non-exhaustive swap once you compile the buyer (most important on this case), it can not accomplish that if the buyer isn’t recompiled when the sealed sort modifications. For instance, suppose that we lengthen our SealedType with one other case:
public sealed interface SealedType {
document TypeA() implements SealedType {}
document TypeB() implements SealedType {}
document TypeC() implements SealedType {}
static SealedType of(String sort) {
return swap (sort) {
case “A” -> new TypeA();
case “B” -> new TypeB();
case “C” -> new TypeC();
default -> throw new IllegalArgumentException();
};
}
}
If we simply recompiled SealedType.java and don’t recompile Main, then we find yourself with a runtime exception if we set off the brand new case:
Here we’ve the brand new MatchException being thrown. The Javadoc notes this potential challenge with separate compilation, and in addition some corner-cases with nulls in patterns. So even for those who have been hoping that utilizing sealed courses would statically be sure that you replace all shoppers when a brand new case is added, this isn’t the case until you recompile every part.
Conclusions
I believe for me the conclusion is that sealed varieties are most likely most helpful inside the implementation of a part, and are much less helpful when uncovered within the public API {that a} part affords to different parts (eg a library). For inside use, the place you usually are going to recompile every part collectively, you get the great properties of exhaustiveness checking and better compile-time security ensures. But when used throughout module boundaries, you might simply be introducing new methods to interrupt code, usually solely detectable at runtime.
(I found these subtleties when reviewing the preview support for PEM-encoded cryptographic objects, which makes precisely this error of baking a sealed interface right into a public API and advocate purchasers to sample match towards that sort. A predict a really excessive likelihood of breakage in the event that they ever wish to add a brand new case).
