Abstract: Sealed classes
Sealed classes and interfaces restrict which other classes or interfaces may extend or implement them. By supporting sealed classes and interfaces, the Groovy programming language can offer an additional mechanism for controlling class hierarchy construction.
Inheritance is a powerful mechanism for creating hierarchies of related class and interfaces. Sometimes, it is desirable to restrict the definition of children in such hierarchies. Modifiers already provide some mechanisms:
If all of our classes and interfaces are public, this indicates that we want maximum reuse.
finalmodifier offers one mechanism for restricting further inheritance at the method or class level. It effectively limits all further extension and indicates no further code reuse is desired.
By making a base class package-private we can limit extension to only classes within the same package. If an abstract
Shapeclass is package-private, I could have public classes
Circlein the same package. This indicates that we want code reuse to occur only within the package. While it does limit creation of new shapes outside the original package, it offers no abstraction for a shape which could be either a square or circle since
Shapeis not public.
We can use
protectedvisibility to limit access of members strictly to children but that doesn’t help us solve the aforementioned problems like lack of a visible abstraction for
Shapein the discussed example.
Sealed classes or interfaces can be public but have an associated list of allowed children. Classes or interfaces which are not in that list cannot inherit from those sealed types. This indicates that we want code reuse within the hierarchy but not beyond. Parent classes in the hierarchy can be made accessible, without also making them extensible. This allows hierarchies to be created with maximum reuse within but without having to defensively code for arbitrary extensions added at a later time.
Such classes are useful in defining Algebraic Data Types (ADTs) and in scenarios where we might want to reason about whether we have accounted for all possible types, e.g. the static compiler may wish to give a warning if a switch block doesn’t exhaustively cover all possible types by respective case branches.
@Sealedmarker annotation or AST transform which allows a list of permitted children to be defined. Use of this annotation will be an incubating feature subject to change. Explicit use may eventually be discouraged and instead a keyword, e.g.
sealedwould be encouraged instead. However, the annotation could be retained to offer support for this feature on earlier JVMs or versions of Groovy prior to any grammar changes.
Prohibit extension of JDK16+ sealed classes or annotated
@Sealedclasses. Likewise for interfaces. This also applies for anonymous inner classes and traits.
Provide checks in other places where such extension might occur implicitly, e.g.: with
@Delegate, when using type coercion, etc.
unsealedsub-hierarchies. (See JEP-409)
Allow the permitted subclasses to be inferred automatically just for the case where the base and all permitted subclasses are in the same file. (See JEP-409)
The following potential extensions are possibly all desirable but are non-goals for the first implementation:
permitsclause in the grammar.
Require that all classes within a sealed hierarchy be compiled at the same time.
Require that all classes within a sealed hierarchy belong to the same JPMS module.
Add warnings to the static compiler if a switch is used for a sealed hierarchy and not all types are exhaustively covered.
When running on JDK16+, also add sealed class information into the bytecode.
References and useful links
1 (2021-07-22) Initial draft