Sealed Classes in Java
What
- Sum types (a subset of Algebraic data types)
- A way to restrict class hierarchy downstream by specifying which classes can extend a class.
Why
- Exhaustive pattern matching
- (Sort of) hierarchical Enums
- Lock down/selectively permit hierarchy
- More expressive type-system than access-control system
Constraints
- The sealed class and its permitted subclasses must belong to the same module, and, if declared in an unnamed module, to the same package.
- Every permitted subclass must directly extend the sealed class.
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 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. (The modifier non-sealed is the first hyphenated keyword proposed for Java.)
How
- Basic syntax
1package com.example.geometry;
2
3public abstract sealed class Shape
4 permits com.example.polar.Circle,
5 com.example.quad.Rectangle,
6 com.example.quad.simple.Square { ... }
- Extensive example
1package com.example.geometry;
2
3public abstract sealed class Shape
4 permits Circle, Rectangle, Square, WeirdShape { ... }
5
6public final class Circle extends Shape { ... }
7
8public sealed class Rectangle extends Shape
9 permits TransparentRectangle, FilledRectangle { ... }
10public final class TransparentRectangle extends Rectangle { ... }
11public final class FilledRectangle extends Rectangle { ... }
12
13public final class Square extends Shape { ... }
14
15public non-sealed class WeirdShape extends Shape { ... }
- Sealed interfaces
1package com.example.expression;
2
3public sealed interface Expr
4 permits ConstantExpr, PlusExpr, TimesExpr, NegExpr { ... }
5
6public final class ConstantExpr implements Expr { ... }
7public final class PlusExpr implements Expr { ... }
8public final class TimesExpr implements Expr { ... }
9public final class NegExpr implements Expr { ... }
- Another interface example
1interface I {}
2sealed class C permits D, E {}
3non-sealed class D extends C {}
4final class E extends C {}
5
6void test (C c) {
7 if (c instanceof I)
8 System.out.println("It's an I");
9}
- Sealing and record classes
1package com.example.expression;
2
3public sealed interface Expr
4 permits ConstantExpr, PlusExpr, TimesExpr, NegExpr { ... }
5
6public record ConstantExpr(int i) implements Expr { ... }
7public record PlusExpr(Expr a, Expr b) implements Expr { ... }
8public record TimesExpr(Expr a, Expr b) implements Expr { ... }
9public record NegExpr(Expr e) implements Expr { ... }
- Sealed classes and pattern matching
1Shape rotate(Shape shape, double angle) {
2 return switch (shape) { // pattern matching switch
3 case Circle c -> c;
4 case Rectangle r -> shape.rotate(angle);
5 case Square s -> shape.rotate(angle);
6 // no default needed!
7 }
8}
- Java grammar after extension
1NormalClassDeclaration:
2 {ClassModifier} class TypeIdentifier [TypeParameters]
3 [Superclass] [Superinterfaces] [PermittedSubclasses] ClassBody
4
5ClassModifier:
6 (one of)
7 Annotation public protected private
8 abstract static sealed final non-sealed strictfp
9
10PermittedSubclasses:
11 permits ClassTypeList
12
13ClassTypeList:
14 ClassType {, ClassType}