JDK-8246775 : JEP 397: Sealed Classes (Second Preview)
  • Type: JEP
  • Component: specification
  • Sub-Component: language
  • Priority: P3
  • Status: Closed
  • Resolution: Delivered
  • Fix Versions: 16
  • Submitted: 2020-06-08
  • Updated: 2022-03-11
  • Resolved: 2021-02-02
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Sub Tasks
JDK-8246776 :  
JDK-8246777 :  
JDK-8246778 :  
JDK-8262150 :  
Description
Summary
-------

Enhance the Java programming language with 
[sealed classes and interfaces](https://cr.openjdk.java.net/~briangoetz/amber/datum.html). Sealed classes and interfaces restrict which other classes or interfaces may extend or implement them. This is a [preview language feature](http://openjdk.java.net/jeps/12) in JDK 16.

History
--------

Sealed Classes were proposed by [JEP 360](https://openjdk.java.net/jeps/360) and
delivered in [JDK 15](https://openjdk.java.net/projects/jdk/15) as a 
[preview feature](https://openjdk.java.net/jeps/12). 

This JEP proposes to re-preview the feature in JDK 16, with the following refinements:

- Specify the notion of a _contextual keyword_, superseding the prior notions
of _restricted identifier_ and _restricted keyword_ in the JLS. Introduce the
character sequences `sealed`, `non-sealed`, and `permits` as contextual
keywords.

- As with anonymous classes and lambda expressions, local classes may not be
subclasses of sealed classes when determining the implicitly declared permitted
subclasses of a `sealed` class or `sealed` interface.

- Enhance narrowing reference conversion to perform stricter checking of
cast conversions with respect to sealed type hierarchies.

Goals
-----

 - Allow the author of a class or interface to control which code is responsible for implementing it.

 - Provide a more declarative way than access modifiers to restrict the use of a superclass.

 - Support future directions in [pattern matching](https://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html) by providing a foundation for the exhaustive analysis of patterns.

Non-Goals
---------

 - It is not a goal to provide new forms of access control such as "friends".

 - It is not a goal to change `final` in any way.

Motivation
----------

The object-oriented data model of inheritance hierarchies of classes and
interfaces has proven to be highly effective in modeling the real-world data
processed by modern applications. This expressiveness is an important aspect of
the Java language. 

There are, however, cases where such expressiveness can usefully be tamed. For example, Java supports _enum classes_ to model the situation where a given class has only a fixed number of instances. In the following code, an enum class lists a fixed set of planets. They are the only values of the class, therefore you can switch over them exhaustively — without having to write a `default` clause:

```
enum Planet { MERCURY, VENUS, EARTH }

Planet p = ...
switch (p) {
  case MERCURY: ...
  case VENUS: ...
  case EARTH: ...
}
```

Using enum classes to model fixed sets of values is often helpful, but sometimes we want to model a fixed set of _kinds_ of values. We can do this by using a class hierarchy not as a mechanism for code inheritance and reuse but, rather, as a way to list kinds of values. Building on our planetary example, we might might model the kinds of values in the astronomical domain as follows:

```
interface Celestial { ... }
final class Planet implements Celestial { ... }
final class Star   implements Celestial { ... }
final class Comet  implements Celestial { ... }
```

This hierarchy does not, however, reflect the important domain knowledge that there are only three kinds of celestial objects in our model. In these situations, restricting the set of subclasses or subinterfaces can streamline the modeling.

Consider another example: In a graphics library, the author of a class `Shape`
may intend that only particular classes can extend `Shape`, since much of the
library's work involves handling each kind of shape in the appropriate way. The
author is interested in the clarity of code that handles known subclasses of
`Shape`, and not interested in writing code to defend against unknown
subclasses of `Shape`. Allowing arbitrary classes to extend `Shape`, and thus
inherit its code for reuse, is not a goal in this case. Unfortunately, Java
assumes that code reuse is always a goal: If `Shape` can be extended at all,
then it can be extended by any number of classes. It would be helpful to relax
this assumption so that an author can declare a class hierarchy that is not open
for extension by arbitrary classes. Code reuse would still be possible within
such a closed class hierarchy, but not beyond.  

Java developers are familiar with the idea of restricting the set of subclasses
because it often crops up in API design. The language provides limited tools in
this area: Either make a class `final`, so it has zero subclasses, or make the
class or its constructor package-private, so it can only have subclasses in the
same package. An example of a package-private superclass [appears in the JDK](https://hg.openjdk.java.net/jdk/jdk/file/tip/src/java.base/share/classes/java/lang/):

```
package java.lang;

abstract class AbstractStringBuilder { ... }
public final class StringBuffer  extends AbstractStringBuilder { ... }
public final class StringBuilder extends AbstractStringBuilder { ... }
```

The package-private approach is useful when the goal is code reuse, such as having the subclasses of `AbstractStringBuilder` share its code for `append`. However, the approach is useless when the goal is modeling alternatives, since user code cannot access the key abstraction — the superclass — in order to `switch` over it. Allowing users to access the superclass without also allowing them to extend it cannot be easily specified without resorting to brittle tricks involving non-`public` constructors — which do not work for interfaces. In a graphics library that declares `Shape` and its subclasses, it would be unfortunate if only one package could access `Shape`.

In summary, it should be possible for a superclass to be widely _accessible_ (since it represents an important abstraction for users) but not widely _extensible_ (since its subclasses should be restricted to those known to the author). Such a superclass should be able to express that it is co-developed with a given set of subclasses, both to document intent for the reader and to allow enforcement by the Java compiler. At the same time, the superclass should not unduly constrain its subclasses by, e.g., forcing them to be `final` or preventing them from defining their own state.

Description
-----------

A _sealed_ class or interface can be extended or implemented only by those classes and interfaces permitted to do so.

A class is sealed by applying the `sealed` modifier to its declaration. Then, after any `extends` and `implements` clauses, the `permits` clause specifies the classes that are permitted to extend the sealed class. For example, the following declaration of `Shape` specifies three permitted subclasses:

```
package com.example.geometry;

public abstract sealed class Shape
    permits Circle, Rectangle, Square { ... }
```

The classes specified by `permits` must be located near the superclass: either in the same module (if the superclass is in a named module) or in the same package (if the superclass is in the unnamed module). For example, in the following declaration of `Shape`, its permitted subclasses are all located in different packages of the same named module:

```
package com.example.geometry;

public abstract sealed class Shape 
    permits com.example.polar.Circle,
            com.example.quad.Rectangle,
            com.example.quad.simple.Square { ... }
```

When the permitted subclasses are small in size and number, it may be convenient to declare them in the same source file as the sealed class. When they are declared in this way, the `sealed` class may omit the `permits` clause, and the Java compiler will infer the permitted subclasses from the declarations in the source file. (The subclasses may be auxiliary or nested classes.) For example, if the following code is found in `Shape.java`, then the sealed class `Shape` is inferred to have three permitted subclasses:

```
package com.example.geometry;

abstract sealed class Shape { ... }
... class Circle    extends Shape { ... }
... class Rectangle extends Shape { ... }
... class Square    extends Shape { ... }    
```

Sealing a class restricts its subclasses. User code can inspect an instance of a
sealed class with an `if`-`else` chain of `instanceof` tests, one test per
subclass; no catch-all `else` clause is needed. For example, the following code
looks for the three permitted subclasses of `Shape`:

```
Shape rotate(Shape shape, double angle) {
    if (shape instanceof Circle) return shape;
    else if (shape instanceof Rectangle) return shape.rotate(angle);
    else if (shape instanceof Square) return shape.rotate(angle);
    // no else needed!
}
```

A sealed class imposes three constraints on its permitted subclasses:

1. The sealed class and its permitted subclasses must belong to the same module, and, if declared in an unnamed module, to the same package.

2. Every permitted subclass must directly extend the sealed class.

3. Every permitted subclass must use a modifier to describe how it propagates the sealing initiated by its superclass:

      - A permitted subclass may be declared `final` to prevent its part of the
      class hierarchy from being extended further. (Record classes 
      ([JEP 395](https://openjdk.java.net/jeps/395)) are
      implicitly declared `final`.)

      - A permitted subclass may be declared `sealed` to allow its part of the hierarchy to be extended further than envisaged by its sealed superclass, but in a restricted fashion.

      - A permitted subclass may be declared `non-sealed` so that its part of the hierarchy reverts to being open for extension by unknown subclasses. (A sealed class cannot prevent its permitted subclasses from doing this.)

As an example of the third constraint, `Circle` may be `final` while `Rectangle` is `sealed` and `Square` is `non-sealed`:

```
package com.example.geometry;

public abstract sealed class Shape
    permits Circle, Rectangle, Square { ... }

public final class Circle extends Shape { ... }

public sealed class Rectangle extends Shape 
    permits TransparentRectangle, FilledRectangle { ... }
public final class TransparentRectangle extends Rectangle { ... }
public final class FilledRectangle extends Rectangle { ... }

public non-sealed class Square extends Shape { ... }    
```

Exactly one of the modifiers `final`, `sealed`, and `non-sealed` must be
used by each permitted subclass. It is not possible for a class to be both
`sealed` (implying subclasses) and `final` (implying no subclasses),  or both
`non-sealed` (implying subclasses) and `final` (implying no subclasses), or both
`sealed` (implying restricted subclasses) and `non-sealed` (implying
unrestricted subclasses).

(The `final` modifier can be considered a strong form of sealing, where extension/implementation is prohibited completely. That is, `final` is conceptually equivalent to `sealed` + a `permits` clause which specifies nothing, though such a `permits` clause cannot be written.)

A class which is `sealed` or `non-sealed` may be `abstract`, and have `abstract` members. A `sealed` class may permit subclasses which are `abstract`, providing they are then `sealed` or `non-sealed`, rather than `final`.

### Class accessibility

Because `extends` and `permits` clauses make use of class names, a permitted subclass and its sealed superclass must be accessible to each other. However, permitted subclasses need not have the same accessibility as each other, or as the sealed class. In particular, a subclass may be less accessible than the sealed class. This means that, in a future release when pattern matching is supported by switches, some code will not be able to exhaustively `switch` over the subclasses unless a `default` clause (or other total pattern) is used. Java compilers will be encouraged to detect when `switch` is not as exhaustive as its original author imagined it would be, and customize the error message to recommend a `default` clause.

### Sealed interfaces

As for classes, an interface can be sealed by applying the
`sealed` modifier to the interface. After any `extends` clause to specify
superinterfaces, the implementing classes and subinterfaces are specified with a
`permits` clause. For example, the planetary example from the introduction can
be rewritten as follows:

```
sealed interface Celestial 
    permits Planet, Star, Comet { ... }

final class Planet implements Celestial { ... }
final class Star   implements Celestial { ... }
final class Comet  implements Celestial { ... }
```

Here is another classic example of a class hierarchy where there is a known set
of subclasses: modeling mathematical expressions.

```
package com.example.expression;

public sealed interface Expr
    permits ConstantExpr, PlusExpr, TimesExpr, NegExpr { ... }

public final class ConstantExpr implements Expr { ... }
public final class PlusExpr     implements Expr { ... }
public final class TimesExpr    implements Expr { ... }
public final class NegExpr      implements Expr { ... }
```

### Sealing and record classes

Sealed classes work well with record classes ([JEP 395](https://openjdk.java.net/jeps/395)). Record classes are implicitly `final`, so a sealed hierarchy of record classes is slightly more concise than the example above:

```
package com.example.expression;

public sealed interface Expr
    permits ConstantExpr, PlusExpr, TimesExpr, NegExpr { ... }

public record ConstantExpr(int i)       implements Expr { ... }
public record PlusExpr(Expr a, Expr b)  implements Expr { ... }
public record TimesExpr(Expr a, Expr b) implements Expr { ... }
public record NegExpr(Expr e)           implements Expr { ... }
```

The combination of sealed classes and record classes is sometimes referred to as [_algebraic data types_](https://en.wikipedia.org/wiki/Algebraic_data_type): Record classes allow us to express _product types_, and sealed classes allow us to express _sum types_.

### Sealed classes and conversions

A cast expression converts a value to a type.  A type `instanceof` expression
tests a value against a type. Java is extremely permissive about the types that
are allowed in these kinds of expressions. For example:

```
   interface I {}
   class C {} // does not implement I

   void test (C c) {
       if (c instanceof I) 
           System.out.println("It's an I");
   }
```

This program is legal even though it is currently not possible for a `C` object to
implement the interface `I`. Of course, as the program evolves, it might be:

```
   ...
   class B extends C implements I {}

   test(new B()); 
   // Prints "It's an I"
```

The type conversion rules capture a notion of _open extensibility_. The Java
type system does not assume a closed world. Classes and interfaces can be
extended at some future time, and casting conversions compile to runtime tests,
so we can safely be flexible.

However, at the other end of the spectrum the conversion rules do address
the case where a class can definitely not be extended, i.e., when it is a `final` class.

```
   interface I {}
   final class C {}

   void test (C c) {
       if (c instanceof I) 
           System.out.println("It's an I");
   }
```

The method `test` fails to compile, since the compiler knows that there can be no
subclass of `C`, so since `C` does not implement `I` then it is never possible for a `C`
value to implement `I`. This is a compile-time error.

What if `C` is not final, but sealed? Its direct subclasses
are explicitly enumerated, and — by the definition of being sealed — in the same
module, so we expect the compiler to look to see if it can spot a
similar compile-time error. Consider the following code: 

```
   interface I {}
   sealed class C permits D {}
   final class D extends C {}
   
   void test (C c) {
       if (c instanceof I) 
           System.out.println("It's an I");
   }
```

Class `C` does not implement `I`, and is not `final`, so by the existing rules we
might conclude that a conversion is possible. `C` is `sealed`, however, and there
is one permitted direct subclass of `C`, namely `D`. By the definition of sealed
types, `D` must be either `final`, `sealed`, or `non-sealed`. In this example,
all the direct subclasses of `C` are `final` and do not implement `I`. This
program should therefore be rejected, since there cannot be a subtype of `C`
that implements `I`.

In contrast, consider a similar program where one of the direct subclasses of
the sealed class is `non-sealed`:

```
   interface I {}
   sealed class C permits D, E {}
   non-sealed class D extends C {}
   final class E extends C {}

   void test (C c) {
       if (c instanceof I) 
           System.out.println("It's an I");
   }
```   

This is type-correct, since it is possible for a subtype of the `non-sealed`
type `D` to implement `I`.

This JEP will extend the definition of
[narrowing reference conversion](https://docs.oracle.com/javase/specs/jls/se15/html/jls-5.html#jls-5.1.6.1)
to navigate sealed hierarchies to determine at compile time which
conversions are not possible. 

### Sealed classes in the JDK

An example of how sealed classes might be used in the JDK is in the `java.lang.constant` package that models [descriptors for JVM entities](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/constant/package-summary.html):

```
package java.lang.constant;

public sealed interface ConstantDesc
    permits String, Integer, Float, Long, Double,
            ClassDesc, MethodTypeDesc, DynamicConstantDesc { ... }

// ClassDesc is designed for subclassing by JDK classes only
public sealed interface ClassDesc extends ConstantDesc
    permits PrimitiveClassDescImpl, ReferenceClassDescImpl { ... }
final class PrimitiveClassDescImpl implements ClassDesc { ... }
final class ReferenceClassDescImpl implements ClassDesc { ... } 

// MethodTypeDesc is designed for subclassing by JDK classes only
public sealed interface MethodTypeDesc extends ConstantDesc
    permits MethodTypeDescImpl { ... }
final class MethodTypeDescImpl implements MethodTypeDesc { ... }

// DynamicConstantDesc is designed for subclassing by user code
public non-sealed abstract class DynamicConstantDesc implements ConstantDesc { ... }
```

### Sealed classes and pattern matching

A significant benefit of sealed classes will be realized in a future release in
conjunction with 
[pattern matching](https://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html).
Instead of inspecting an instance of a sealed class with `if`-`else` chains,
user code will be able to use a `switch` enhanced with _type test patterns_.
This will allow the Java compiler to check that the patterns are exhaustive. 

For example, consider this code from earlier:

```
Shape rotate(Shape shape, double angle) {
    if (shape instanceof Circle) return shape;
    else if (shape instanceof Rectangle) return shape.rotate(angle);
    else if (shape instanceof Square) return shape.rotate(angle);
    // no else needed!
}
```

The Java compiler cannot ensure that the `instanceof` tests cover
all the permitted subclasses of `Shape`. For example, no compile-time error
message would be issued if the `instanceof Rectangle` test was omitted.

In contrast, in the following code that uses a pattern matching `switch`
expression, the
compiler can confirm that every permitted subclass of `Shape` is covered, so no
`default` clause (or other total pattern) is needed. The compiler will, moreover,
issue an error message if any of the three cases are missing:

```
Shape rotate(Shape shape, double angle) {
    return switch (shape) {   // pattern matching switch
        case Circle c    -> c; 
        case Rectangle r -> r.rotate(angle);
        case Square s    -> s.rotate(angle);
        // no default needed!
    }
} 
```

### Java Grammar

The grammar for class declarations is amended to the following:

```
NormalClassDeclaration:
  {ClassModifier} class TypeIdentifier [TypeParameters]
    [Superclass] [Superinterfaces] [PermittedSubclasses] ClassBody

ClassModifier:
  (one of)
  Annotation public protected private
  abstract static sealed final non-sealed strictfp

PermittedSubclasses:
  permits ClassTypeList

ClassTypeList:
  ClassType {, ClassType}
```

### JVM support for sealed classes

The Java Virtual Machine recognizes `sealed` classes and interfaces at runtime, and prevents extension by unauthorized subclasses and subinterfaces.

Although `sealed` is a class modifier, there is no `ACC_SEALED` flag in the `ClassFile` structure. Instead, the `class` file of a sealed class has a `PermittedSubclasses` attribute which implicitly indicates the `sealed` modifier and explicitly specifies the permitted subclasses:

```
PermittedSubclasses_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 number_of_classes;
    u2 classes[number_of_classes];
}
```

The list of permitted subclasses is mandatory. Even when the permitted subclasses are inferred by the compiler, those inferred subclasses are explicitly included in the `PermittedSubclasses` attribute.

The `class` file of a permitted subclass carries no new attributes.

When the JVM attempts to define a class whose superclass or superinterface has a `PermittedSubclasses` attribute, the class being defined must be named by the attribute. Otherwise, an `IncompatibleClassChangeError` is thrown.

### Reflection API

We will add the following `public` methods to `java.lang.Class`:

 - `java.lang.Class[] getPermittedSubclasses()` 
 - `boolean isSealed()` 

The method `getPermittedSubclasses()` returns an array containing `java.lang.Class` objects representing the permitted subclasses of the class, if the class is sealed.  It returns an empty array if the class is not sealed.

The method `isSealed` returns true if the given class or interface is sealed. (Compare with `isEnum`.)

Alternatives
------------

Some languages have direct support for _[algebraic data types](https://en.wikipedia.org/wiki/Algebraic_data_type)_ (ADTs), such as Haskell's `data` feature.  It would be possible to express ADTs more directly and in a manner familiar to Java developers through a variant of the `enum` feature, where a sum of products could be defined in a single declaration.  However, this would not support all the desired use cases, such as those where sums range over classes in more than one compilation unit, or sums that range over classes that are not products.

The `permits` clause allows a sealed class, such as the `Shape` class shown earlier, to be accessible-for-invocation by code in any module, but accessible-for-implementation by code in only the same module as the sealed class (or same package if in the unnamed module). This makes the type system more expressive than the access-control system. With access control alone, if `Shape` is accessible-for-invocation by code in any module (because its package is exported), then `Shape` is also accessible-for-implementation in any module; and if `Shape` is not accessible-for-implementation in any other module, then `Shape` is also not accessible-for-invocation in any other module.

Dependencies
-----------

Sealed classes do not depend on record classes ([JEP 395](https://openjdk.java.net/jeps/395)) or pattern matching ([JEP 394](https://openjdk.java.net/jeps/394)), but they work well with both.

Comments
Thanks Mark. I've moved the pattern matching switch bit into a new, later section so it doesn't distract from the flow. Thanks for the suggestion! Assigning back to you!
29-10-2020

I’ve done a light copy-editing pass. If this looks okay to you then reassign the issue to me and I’ll move it to Candidate. The benefit that you show of switching on Shape instances can be had without using type-test patterns, so unless I’m missing something you can simplify that example. You needn’t get into pattern matching at all at that point.
28-10-2020