Thursday, September 28, 2023

Java 17: Introducing the Sealed Classes

5 min read
(Updated: Thursday, September 28, 2023)

1. 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:

Java
sealed class Shape permits Circle, Rectangle, Triangle { ... } 

The subclasses listed in the permits clause must be one of the following:

  1. A final class: This class cannot be further extended.
  2. A sealed class: This class can have a limited set of subclasses of its own.
  3. 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.

Java
// 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:

Java
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:

Java
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.

Java
// 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

  1. 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.
  2. 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.
  3. 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.

You might want to check these out ↘

How Your Database Stands Up to the Ultimate Reliability Check: ACID rules

How Your Database Stands Up to the Ultimate Reliability Check: ACID rules

For IT professionals in database management, grasping the core principles that protect our digital transactions is essential. Today, we're exploring the ACID model, a pivotal framework for ensuring transaction reliability and security. This goes beyond mere theoretical knowledge; it's vital for the smooth operation of diverse systems, from financial processes to social media platforms. We'll delve into the ACID principles and their critical role in real-world applications.
Convert String to Date: The Java Ways

Convert String to Date: The Java Ways

Converting strings to dates in programming is a common task that's more complex than it seems. This challenge is rooted in the wide range of date formats and the specifics of time zones. Converting a String to a Date in Java can be done in several ways, depending on the Java version you're using and whether you are incorporating external libraries. Below are the primary methods used in different versions of Java and through some commonly used external libraries.
Cookbook: How to Effectively Revert a Git Commit

Cookbook: How to Effectively Revert a Git Commit

As a software engineer, encountering situations where recent changes need to be undone is not uncommon. It may be due to errors, or in some cases just need a significant rework. When such scenarios arise while using Git as your version control system, there’s a few approaches that can help revert those changes effectively. Let’s take a closer look.
Technology
Trending
Contact Us

Stay ahead of the curve
on software insights and technology trends.