Java sealed classes and exhaustive pattern matching

Hacker News by 4 min read 67x views
Java sealed classes and exhaustive pattern matching

Share Post

Java 17 introduced sealed classes, which authorize you to explicitly catalog the allowed sub-types of an interface or basis class. For example, here’s a toy example using a sealed interface and records (inner classes are implicitly added to the permitted sub-types if an definitive catalog is not given):

 public sealed interface SealedType { document TypeA() implements SealedType {} document TypeB() implements SealedType {} fixed SealedType of(String type) { come back toggle (type) { case “A” -> new TypeA(); case “B” -> new TypeB(); default -> throw new IllegalArgumentException(); }; } } 

If you are acquainted alongside functional programming languages alongside algebraic datatypes, you can perspective this as akin to a datatype declaration in Haskell or ML:

 data SealedType = TypeA | TypeB 

We can afterward use this in a uncomplicated Main class:

 void main(String[] args) { var val = SealedType.of(args[0]); System.out.println(switch (val) { case SealedType.TypeA() -> “A”; case SealedType.TypeB() -> “B”; }); } 

OK, not so exciting. But one item to note current is that we didn’t have to add a default clause to the toggle expression in our chief method. This is since sealed classes (and enums) allow exhaustiveness checking: the compiler knows exactly what the imaginable cases are, and so can inspect if you have covered them all. If you have, afterward you don’t need a default clause. If you ignore one (and don’t have a default clause), afterward you get a compile-time error.

This is awesome whenever you desire to justify that all uses of several category do shield all of the cases, but it does current a new category of breaking change: adding a new sub-type to a sealed class/interface may interrupt consumers of that code. For example, adding a new TypeC case to our example volition logic the chief method to neglect to compile because of the missing case. So if you export a sealed category in your API afterward adding a new subtype is a breaking alter that would necessitate a important type bump (if you’re following SemVer).

Compile-time or runtime error?

Although Java volition create a compile-time error for a non-exhaustive toggle whenever you compile the person (main in this case), it cannot do so if the person is not recompiled whenever the sealed category changes. For example, assume that we broaden our SealedType alongside another case:

 public sealed interface SealedType { document TypeA() implements SealedType {} document TypeB() implements SealedType {} document TypeC() implements SealedType {} fixed SealedType of(String type) { come back toggle (type) { case “A” -> new TypeA(); case “B” -> new TypeB(); case “C” -> new TypeC(); default -> throw new IllegalArgumentException(); }; } } 

If we fair recompiled SealedType.java and don’t recompile Main, afterward we end up alongside a runtime exception if we trigger the new case:

Here we have the new MatchException being thrown. The Javadoc notes this possible matter alongside distinct compilation, and additionally several corner-cases alongside nulls in patterns. So equal if you were expecting that using sealed classes would statically justify that you update all consumers whenever a new case is added, this is not the case unless you recompile everything.

Conclusions

I think for me the decision is that sealed types are likely most helpful within the implementation of a component, and are small helpful whenever exposed in the community API that a component offers to another components (eg a library). For inner use, anywhere you typically are going to recompile everything together, you get the nice properties of exhaustiveness checking and higher compile-time safety guarantees. But whenever used throughout component boundaries, you may fair be introducing new ways to interrupt code, frequently lone detectable at runtime.

(I discovered these subtleties whenever reviewing the preview assistance for PEM-encoded cryptographic objects, which makes exactly this error of baking a sealed interface into a community API and propose clients to form equivalent against that type. A foretell a extremely elevated chance of breakage if they always desire to add a new case).

Other Article Hacker News
↑
Close Right Ads
Close Left Ads