1. Introduction
In this tutorial, we’ll discuss a very important introduction in Java language, sealed
classes and interfaces.
The enums
in Java assist in modeling fixed set of values. But sometimes we need to model fixed kind of values. For example,
interface Vehicle { ... } final class Car implements Vehicle { ... } final class Truck implements Vehicle { ... } final class Bus implements Vehicle { ... }
Here, there are three types of Vehicle
. To restrict the kind of Vehicle
, we have to restrict the set of subclasses or subinterfaces.
Sealed
classes allow to define a closed class hierarchy where the class is not open for extension by arbitrary classes.
Before sealed classes, Java provided limited options to achieve this: either make the class as final
or make the class or its constructor package-private, so it can only have subclasses in the same package. One point to note here is that the private constructor approach does not work with interfaces.
Sealed class makes it possible for a superclass to be accessible but not widely extensible. Subclasses here are not constrained to be declared as final
or declaring their own state.
Sealed classes and interfaces limit the set of classes or interfaces that are allowed to extend or implement them. Sealed classes were proposed by JEP 360 and delivered in JDK 15 as preview feature. In JDK 16 were still the preview feature and finalized in JDK 17. Sealed classes provide more declarative way than access modifiers to restrict the use of superclass.
Sealed classes do not intend to change the final
in Java.
2. Description
To seal a class, you use the sealed
modifier in its declaration. Following any extends
and implements
clauses, you add a permits
clause to list the specific classes allowed to extend the sealed class.
For example, the following declaration of Shape
specifies three permitted subclasses:
public abstract sealed class Shape permits Circle, Rectangle, Square { ... }
Classes listed in the permits
clause must be located either within the same module (if the superclass is part of a named module) or within the same package (if the superclass belongs to an unnamed module).
If the permitted subclasses are few and small, it can be convenient to declare them in the same source file as the sealed
class. In this case, the sealed
class can omit the permits
clause, and the Java compiler will automatically infer the permitted subclasses from the declarations within the source file.
For example, if the following code is found in Vehicle.java
then the sealed class Vehicle
is inferred to have three permitted subclasses:
abstract sealed class Vehicle { ... final class Car extends Vehicle { ... } final class Bus extends Vehicle { ... } final class Truck extends Vehicle { ... } }
Anonymous classes and local classes cannot be permitted subtypes of a sealed
class because classes specified by permits
must have a canonical name.
When a class is declared as sealed
, it implies that it can be have subclasses whereas final
implies that a class can not have subclasses. Therefore following combinations are invalid for a class:
sealed
andfinal
non-sealed
andfinal
sealed
andnon-sealed
, because a class can not have restricted and unrestricted subclasses at the same time.
A class, whether sealed
or non-sealed
, can be abstract and contain abstract members. A sealed
class can allow abstract subclasses, provided these subclasses are either sealed
or non-sealed
, but not final
.
Since the extends
and permits
clauses use class names, a permitted subclass and its sealed
superclass must be mutually accessible.
Here is an example from Java documentation:
public abstract sealed class Shape permits Circle, Rectangle, Square, WeirdShape { ... } 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 final class Square extends Shape { ... } public non-sealed class WeirdShape extends Shape { ... } public non-sealed class WeirdShape extends Shape { ... }
3. Constraints on permitted classes
A sealed class imposes three constraints on its permitted subclasses:
- 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 specify how it propagates the sealing initiated by its superclass:
final: The subclass cannot be extended further.
sealed: The subclass can be extended, but only by a specific set of classes.
non-sealed: A permitted subclass can be declared asnon-sealed
, making that part of the hierarchy open for extension by any subclasses, known or unknown. Asealed
class cannot prevent its permitted subclasses from doing this.
Exactly one of the modifiers final
, sealed
, and non-sealed
must be used by each permitted subclass.
4. Sealed classes and Reflection API
Following public
methods were added to java.lang.Class
:
java.lang.constant.ClassDesc[] permittedSubclasses()
: Returns an array ofjava.lang.constant.ClassDesc
objects representing all permitted subclasses of the class if it issealed
. If the class is notsealed
, an empty array is returned.boolean isSealed()
: Returnstrue
if the specified class or interface is sealed; otherwise, returnsfalse
.
5. Sealed interfaces
Same as classes, an interface can be sealed by applying the sealed
modifier to the interface. The permits
clause is added after the extends
clause. For example:
sealed interface Celestial permits Planet, Star, Comet { ... } final class Planet implements Celestial { ... } final class Star implements Celestial { ... } final class Comet implements Celestial { ... }
6. Sealing and record classes
You can name a record class in the permits clause of a sealed class or interface. Record classes are implicitly final
.
7. Sealed classes in JDK
Following are examples from JDK:
java.lang.constant.ClassDesc
java.lang.constant.MethodHandleDesc
8. Casting with Sealed classes
The compiler has been enhanced to navigate any sealed hierarchy to check if your cast statements are allowed or not. So if a class is sealed then casting with any disjoint type will give error at compile time. For example:
public sealed interface Shape permits Polygon { } public non-sealed interface Polygon extends Shape { } public final class Vehicle { } public class Toy { } public void work(Shape s) { Vehicle u = (Vehicle) s; // Error Toy t = (Toy) s; // Permitted }
Here, Vehicle u = (Vehicle) s;
is not allowed because only Polygon
is permitted by Shape
.
9. Conclusion
Sealed classes in Java, offer a powerful way to manage and restrict inheritance hierarchies. By explicitly specifying which classes or interfaces can extend or implement a sealed class or interface, developers gain greater control over their code’s architecture, improving maintainability and security. This feature enables more predictable code behavior and helps prevent unauthorized extensions.
Sealed classes are particularly useful in designing APIs and libraries where controlled extension is crucial. By understanding and leveraging sealed classes, developers can create more robust and well-defined class hierarchies, leading to cleaner and more reliable codebases.