Introduction
One of the main advantages of inheritance is code reusability where we can create a new class by extending an existing class and re-use the existing behavior if any or derive a new behavior by overriding the abstract methods, something like the Template Method Pattern.
To revise, a Template Method Pattern is a behavioral design pattern that helps in defining the algorithm in a base class and allows the subclasses to override the existing algorithm steps without changing the algorithm.
But during modeling, we don’t want any arbitrary class to extend a specific class and want to explicitly specify which classes can sub-type the main model class. Say we have an Account class, and we only want SavingsAccount and CheckingAccount classes should extend it, this control we can achieve it through Sealed classes. The sealed class feature was released in Java 17.
The article explains for example what are sealed classes in Java, Sealed Interfaces, and explains one of the most important preview features of Java 17 “Pattern Matching” and how it works well with Sealed classes.
Defining Sealed Classes
In Java, a class can be sealed using the ‘sealed’ modifier and using the ‘permit’ clause we can specify the classes that are allowed to extend the Sealed class.
public sealed class Account permits SavingsAccount, CheckingAccount, MoneyMarketDepositAccount {}
Sub classes
public final class SavingsAccount extends Account {}
public final class CheckingAccount extends Account {}
public non-sealed class MoneyMarketDepositAccount extends Account {}
# The child classes in turn can be either final, non-sealed, or sealed. In the example above we can see that ‘SavingsAccount/CheckingAccount’ are final, but MoneyMarketDepositAccount class is non-sealed. Let’s understand the implications of using three different modifiers.
- final: If the class is final, that means it cannot be extended further.
- sealed: As we have seen, can only be extended by the permitted sub-classes.
- non-sealed: Can be extended by the Unknown subclasses, sealed class cannot control this unknown sub-class
public final class UnknownAccount extends MoneyMarketDepositAccount {}
The ‘UnknownAccount’ is totally a new class and we have not specified it in the permit class.
# The classes must be in the same module or in the same package, for example, if any child class is declared in a different package other than the package where the sealed class is, we will receive the below compilation error
Sealed type Account and subtype “ClassName” in an unnamed module should be declared in the same package xxx.xxx.xx.
Sealed Interfaces
Sealed Interfaces are declared exactly the same way as the Sealed classes.
public sealed interface Polygon permits Triangle, Quadrilateral {}
public final class Triangle implements Polygon {}
public final class Quadrilateral implements Polygon {}
Since Polygon is an interface that’s why it was implemented not extended.
Record Classes
The record classes in Java are introduced in version 16. They are like the container classes with auto-generated Setters / Getters, constructor, equals, hashcode, toString methods. They are very much created for the replacement of Lombok, the record classes are also permitted with Sealed classes or Interface.
public sealed interface Polygon permits Triangle, Quadrilateral, Pentagon {}
public final record Pentagon() implements Polygon {}
Pattern Matching
In Java 17 Pattern Matching is a preview feature and from Java 18 onwards it will be a regular feature. Eclipse users, we need to explicitly enable the preview feature.
Wikipedia's definition of Pattern Matching is In computer science, pattern matching is the act of checking a given sequence of tokens for the presence of the constituents of some pattern.
Pattern matching also works with Objects and with Sealed classes we can write a more concise pattern matching blocks with a family of known objects as they must be limited in terms of numbers.
@SuppressWarnings("preview")
public static String patternMatch(Account obj) {
return switch (obj) {
case SavingsAccount sa - > "Savings Account";
case CheckingAccount ca - > "Checking Account";
case MoneyMarketDepositAccount mmd - > "Money Market Deposit Account";
default - > "Not Found";
};
}
public static void main(String[] args) {
System.out.println("Object Type:" + patternMatch(new CheckingAccount()));
}
Summary
The article explained how sealed classes help in restricting the extensibility, it provides the classes with more control over the allowed sub-types which we have explored using the Account class example. The article also explained the Sealing of Interfaces along with record classes and a Pattern Matching example.