The other answers have the title question down. ("What are sealed classes in Java 17?") But your question in the body ("What is the use of sealed classes?") hasn't really been addressed.
Sealed classes/interfaces are a way to create a tagged union. Tagged unions are to classes what Java enums are to objects.
Java enums let you limit the possible objects a class can instantiate to a specific set of values. This helps you model days of the week like this:
enum Day {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY;
}
boolean isWeekday(Day day) {
switch (day) {
case SUNDAY:
case SATURDAY:
return true;
default:
return false;
}
instead of this:
boolean isWeekday(int day) {
switch (day) {
case 0:
case 6:
return true;
case 1:
case 2:
case 3:
case 4:
case 5:
return false;
default:
throw new IllegalArgumentException("day must be between 0 and 6");
}
If you have an enum type, then you know you have every possible valid value. You can guarantee that your switch statements exhaustively handle all inputs.
The limitation of enums is that they only apply to objects of a single class. Every enum value must have the same instance variables, the same methods, and the same constructor. Each enum value is a single object of the same class.
Sealed classes/interfaces overcome this limitation. Since each subclass is its own class, you can vary anything that you could vary in classes, e.g. instance variables, methods, constructors, additional implemented interfaces. Here's an example:
sealed interface UpsertUserResponse
permits UserCreated, UserUpdated, InvalidEmail, Unauthorized {
}
record UserCreated(UUID id) implements UpsertUserResponse {}
record UserUpdated(String oldEmail) implements UpsertUserResponse {}
record InvalidEmail(String reason) implements UpsertUserResponse {}
record Unauthorized implements UpsertUserResponse {}
String getResponseMessage(UpsertUserResponse response) {
return switch (shape) {
case UserCreated id -> "New user created with id " + id.toString();
case UserUpdated oldEmail -> "Email updated from previous value of " + oldEmail;
case InvalidEmail reason -> "The email you entered was invalid because " + reason;
case Unauthorized -> "You can't do that!"
}
}
You know that your case statement has handled every possible case. And you have the flexibility to support different user ids and email validation errors without resorting to nullable variables that could be in an impossible state. (For example, there's no way to mistakenly set both a user id and a validation error.)
How do you mock out subclasses of sealed classes/interfaces in tests? You don't. Sealed classes/interfaces pair well with plain data aggregates, such as records. You don't write mocks for enums and you don't write mocks for sealed classes.
sealed
classes. – Transcendentalisticprivate
, whereprotected
would have allowed meaningful, but unanticipated extension. Again, some real world use cases would shed more light. – Leanora