Java 17: Introducing the Sealed Classes
5 min read1. Introduction
Inheritance has been one of the core principles in the Java programming language. In old JDKs, there have been numerous ways for developers to restrict and control inheritance. One straightforward example is by providing the "final" keyword. But such approach does not manage or provide clarity on the predictable inheritance at granular level.
2. What are Sealed Classes?
Sealed classes are a new type of class introduced in Java 17 as a preview feature. They provide developers with the ability to define a restricted hierarchy of classes, allowing more controlled and predictable inheritance. Sealed classes restrict the number of subclasses that can extend or implement them, which helps to prevent unanticipated subclassing and promotes better encapsulation.
3. Sealed Class Syntax
To declare a sealed class, use the sealed keyword followed by the permits clause, listing the permitted subclasses:
sealed class Shape permits Circle, Rectangle, Triangle { ... }
The subclasses listed in the permits clause must be one of the following:
- A final class: This class cannot be further extended.
- A sealed class: This class can have a limited set of subclasses of its own.
- A non-sealed class: This class can have any number of subclasses.
4. Real-world Applications
How does the "sealed classes" apply to the real world of programming? Let's take a closer look at some use cases.
4.1 Domain Modeling
Sealed classes are particularly helpful in modeling domain entities that have a limited set of possible subtypes.
For example, in a chess game application, each piece could be represented as a sealed class, with specific subclasses for each type of chess piece.
// Define a sealed class for chess pieces
sealed abstract class ChessPiece permits Pawn, Rook, Knight, Bishop, Queen, King {
private final String color;
public ChessPiece(String color) {
this.color = color;
}
public String getColor() {
return color;
}
}
final class Pawn extends ChessPiece {
public Pawn(String color) {
super(color);
}
}
// Other chess piece classes (Rook, Knight, Bishop, Queen, King) can be defined in the similar way.
4.2 Expression Trees
Sealed classes can be used to build expression trees in compilers or interpreters. They help to represent the nodes of the tree as a restricted hierarchy, making it easy to understand the structure of the code being parsed.
Let's consider a simple example of an expression tree for a mathematical expression like 2 + 3 * 4. We could represent this expression as a tree, with the root node being the addition operator and its two children being the literal value 2 and another expression tree representing the multiplication of 3 and 4.
In Java 17, we could define the nodes of this expression tree using sealed classes. For example, we could define a sealed abstract class Expr that represents the base class for all expression tree nodes, and then define sealed subclasses for each kind of expression node. For instance, we could define the following sealed classes:
sealed abstract class Expr permits Literal, Add, Multiply;
final class Literal extends Expr {
final int value;
public Literal(int value) {
this.value = value;
}
}
final class Add extends Expr {
final Expr left, right;
public Add(Expr left, Expr right) {
this.left = left;
this.right = right;
}
}
final class Multiply extends Expr {
final Expr left, right;
public Multiply(Expr left, Expr right) {
this.left = left;
this.right = right;
}
}
With these classes, we could build the expression tree for 2 + 3 * 4 as follows:
Expr tree = new Add(
new Literal(2),
new Multiply(
new Literal(3),
new Literal(4)
)
);
By using sealed classes to define the expression tree nodes, we ensure that the structure of the expression tree is well-defined and easy to understand. We also get the benefits of type safety and the ability to restrict the set of possible subtypes for each node, which can make it easier to write correct and maintainable code.
5.4 State Machines
Sealed classes work well for modeling state machines with a finite number of states. They allow developers to explicitly define valid state transitions and ensure that the state machine behaves as expected.
// Define a sealed class for states
sealed abstract class State permits OffState, OnState, StandbyState { }
final class OffState extends State {
void turnOn() {
// Logic to turn on the machine
}
}
final class OnState extends State {
void doWork() {
// Logic to perform work
}
void standby() {
// Logic to switch to standby mode
}
void turnOff() {
// Logic to turn off the machine
}
}
final class StandbyState extends State {
void resume() {
// Logic to resume from standby mode
}
void turnOff() {
// Logic to turn off the machine
}
}
5. Benefits of Sealed Classes
- Improved Code Safety: Sealed classes enhance code safety by reducing the risk of unexpected subclassing. They help prevent potential bugs, whereas maintainability issues arising from unintended inheritance.
- Enhanced Code Readability: With a well-defined class hierarchy, it's easier for developers to understand and navigate the codebase. This clarity ultimately improves the maintainability and readability of the code.
- Exhaustive Pattern Matching: Sealed classes work seamlessly with Java's pattern matching features, like switch expressions. They enable the compiler to check for exhaustiveness, ensuring all possible cases are covered, and eliminating the need for a default case.
6. Conclusion
Sealed classes offer an exciting new way to create restricted class hierarchies, providing a host of benefits such as improved code safety, enhanced readability, and exhaustive pattern matching.
By utilizing sealed classes at the right places, it allows us all developers to create more robust, maintainable, and intuitive applications in a wide variety of use cases.