GEP-13


Metadata
Number

GEP-13

Title

Sealed classes

Version

1

Type

Feature

Status

Draft

Leader

Paul King

Created

2021-07-22

Last modification 

2021-07-22

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 classes Square and Circle 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 since Shape 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 for Shape 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 or unsealed 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 and permits 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 a mode 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.

Update history

1 (2021-07-22) Initial draft 2 (2021-11-06) Update to align with 4.0.0-beta-2