I don't really understand why there is a non-sealed
keyword in JEP-360 / Java 15. For me, an extension of a sealed
-class should only be final
or a sealed
-class itself.
Providing the non-sealed
keyword will invite the developer for hacks.
Why are we allowing a sealed
-class to be extended to a non-sealed
-class?
Because in real-world APIs, sometimes we want to support specific extension points while restricting others. The Shape
examples are not particularly evocative, though, which is why it might seem an odd thing to allow.
Sealed classes are about having finer control over who can extend a given extensible type. There are several reasons you might want to do this, and "ensuring that no one extends the hierarchy ever" is only one of them.
There are many cases where an API has several "built in" abstractions and then an "escape hatch" abstraction; this allows API authors to guide would-be extenders to the escape hatches that are designed for extension.
For example, suppose you have a system using the Command
pattern, there are several built-in commands for which you want to control the implementation, and a UserPluginCommand
for extension:
sealed interface Command
permits LoginCommand, LogoutCommand, ShowProfileCommand, UserPluginCommand { ... }
// final implementations of built-in commands
non-sealed abstract class UserPluginCommand extends Command {
// plugin-specific API
}
Such a hierarchy accomplishes two things:
All extension is funneled through the UserPluginCommand
, which can be designed defensively for extension and provide an API suited to user extension, but we can still use interface-based polymorphism in our design, knowing that completely uncontrolled subtypes will not appear;
The system can still rely on the fact that the four permitted types cover all implementations of Command. So internal code can use pattern matching and be confident in its exhaustiveness:
switch (command) {
case LoginCommand(...): ... handle login ...;
case LogoutCommand(...): ... handle logout ...;
case ShowProfileCommand(...): ... handle query ...;
case UserPluginCommand uc:
// interact with plugin API
// no default needed, this switch is exhaustive
There may be a zillion subtypes of UserPluginCommand
, but the system can still confidently reason that it can cover the waterfront with these four cases.
An example of an API that will take advantage of this in the JDK is java.lang.constant
, where there are two subtypes designed for extension -- dynamic constants and dynamic callsites.