GEP-13
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.
Motivation
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.
-
The
final
modifier 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
Shape
class is package-private, we could have public classesSquare
andCircle
in 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 sinceShape
is not public. -
We can use
protected
visibility to limit access of members strictly to children but that doesn’t help us solve the aforementioned problems like lack of a visible abstraction forShape
in 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.
Initial implementation
-
Provide a
@Sealed
marker 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.sealed
would 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 JDK17+ sealed classes or annotated
@Sealed
classes. This also applies for interfaces, 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. -
Support
non-sealed
orunsealed
sub-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)
-
Introduce the
sealed
modifier andpermits
clause in the grammar. -
By default, when running on JDK17+, sealed class information is added into the bytecode. We refer to such classes as native sealed classes.
-
By default, when running on earlier JDKs, an annotation is added to a class to indicate that a class is sealed. Such classes will be recognized by Groovy 4+ compilers but not by Java.
-
The
@SealedOptions
annotation has amode
annotation attribute which can override the default behavior.
Potential extensions
The following potential extensions are possibly all desirable but are non-goals for the first implementation:
-
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.
References and useful links
Reference implementation
Update history
1 (2021-07-22) Initial draft 2 (2021-11-06) Update to align with 4.0.0-beta-2