Shmulik Klein
Software Engineer @ JetBrains | Munich, Germany
Sealed with a Kiss
Java 15 introduced a preview feature called Sealed Classes and Intefaces, which was finalized in Java 17. Sealed classes and interfaces restrict which other classes or interfaces may extend or implement them.
You seal a class/interface by applying the sealed
modifier before the class
/interface
keyword. Than, you have to provide which classes/interface are allowed to inherit/implement the sealed class/interface using the permits
keyword.
public sealed class Sensor permits TempSensor, ForceSensor, PiezoSensor {
...
}
public final class TempSensor extends Sensor {
}
To Seal or not to Seal?
Is sealing a class/interface is really a necssary feature? what does its purpose?
The purpose of sealing a class is to let client code reason clearly and conclusively about all permitted subclasses. JEP-360: Sealed Classes
The JEP states that before sealed classes, the only way the reason about an object sub-type, was to chain instanceof
if-conditions. This old way required the code to have a catch-all else
case, since the compiler couldn’t know if all the subclasses were taken into account. If such a catch-all clause was missing, a compile time error would have been thrown. On the other hand such a clause is redundant, since the user knows which classes are subclasses of the superclasses, he just doesn’t have a way to pass that for the compiler.
void react(Sensor sensor) {
if (sensor instanceof TempSensor) {
return ... // do something with ((TempSensor) sensor)
} else if (sensor instanceof ForceSensor) {
return ... // do something with ((ForceSensor) sensor)
} else if (sensor instanceof PiezoSensor) {
return ... // do something with ((PiezoSensor) sensor)
}
// Compiler, are there any other subclasses that I need to take into accout here?
}
That’s where Pattern Matching and Test Patterns fits in.
Instead of inspecting an instance of a sealed class with if-else, client code will be able to switch over the instance using type test patterns (JEP 375). This allows the compiler to check that the patterns are exhaustive. JEP-360: Sealed Classes
No catch-all clause is required and the compiler can warn us if we missed a subclass of the sealed superclass.
int react(Sensor sensor) {
return switch (shape) {
case TempSensor t -> ... t.getTemp() ...
case ForceSensor f -> ... f.getMass ...
case PiezoSensor p -> ... s.getElectricCharge() ...
};
}
A more compact and expressive code + no need for the explict subclass cast