What are sealed classes in Java 17?
Asked Answered
E

10

60

Today, I updated my Java version from 16 to 17, and I found that sealed classes is a new feature in it. I think it can be declared like this:

public sealed class Main permits AClass, AnotherClass {
}

But, what is the use of sealed classes in Java?

I also knew that it was a preview feature in JDK 15.

Extraterrestrial answered 17/9, 2021 at 6:18 Comment(6)
Just a few days ago, this article was released about this very topic. It may give you an idea about sealed classes.Transcendentalistic
To paraphrase Douglas Adams: "In the beginning [inheritance] was [invented]. This has made a lot of people very angry and been widely regarded as a bad move."Kestrel
Here's a more authoritative article: infoq.com/articles/java-sealed-classesUncovered
Re "In the beginning...": A clip (at 1 min 10 secs).Swivel
By sealing a class, you can specify which classes are permitted to extend it. In this way, you prevent any other arbitrary classes from extending it.Thenar
Any real world examples? I haven't experienced all these problems of inheritance and privacy that everyone worries so much about. Rather the opposite. I have seen entire libraries re-implemented within the same organization because of certain methods or fields declared private, where protected would have allowed meaningful, but unanticipated extension. Again, some real world use cases would shed more light.Leanora
Z
48

You can follow this link for examples.

In short sealed classes gives you the control of which models, classes etc. that can implement or extend that class/interface.

Example from the link:

public sealed interface Service permits Car, Truck {

    int getMaxServiceIntervalInMonths();

    default int getMaxDistanceBetweenServicesInKilometers() {
        return 100000;
    }
}

This interface only permits Car and Truck to implement it.

Zest answered 17/9, 2021 at 6:29 Comment(9)
There is one question which puzzles me. Why it is useful to restrict the amount of implementations. It will make troubles for writing unit tests and I don't see real advantages...Vassar
I don't see how this will make it harder to write unit tests, please elaborate. Writing with sealed classes makes it very clear what the intents of the class or interface is and could help avoid some unfortunate things in larger code repositories with a large amount of developers working on it.Zest
One need workarounds creating mock objects for sealed classes. I see, the clear structure is a nice thing, but if it as useful, that we need a special keyword. We have already "final" with very similar purpose...Vassar
Oracle/JDK does not cares about whether the objects can be mocked or not! final keyword on interface will led Oracle to work upon their existing internal implementation of "Class A implements B" and can be confusing as well.Betimes
@Vassar Related - now that Kotlin is moving to Java 17 byte code (in Kotlin 1.7), we are seeing just such a case: github.com/mockk/mockk/issues/832#issuecomment-1184975908Menology
At the surface the more concerning thing about sealed classes is that higher-level interfaces has knowledge of lower-level concepts such as Car IMO. I do not fully understand why this is needed to allow for pattern matching, as the compiler already knows who implements the interface during compile-time, no?Ferne
@CalleBergström But a non-sealed interface can be implemented by anyone at any time, including by a project developed after a pattern-matching switch on that interface was compiled. The compiler can't possibly know about implementations that don't even exist yet. Using sealed works much like an enum in this case—it allows the compiler to enforce completeness.Culbreth
@Vassar One common pattern in Kotlin is to have a sealed "result class" that has a set number of subclasses indicating different kinds of results, allowing you to react to the different results via a when expression. I suspect that could be one use case for sealed interfaces/classes in Java as well. Another possible use case is to have a common sealed interface with a few non-sealed sub-interfaces, because your API design demands you only implement the sub-interfaces, not the common interface directly. That may not be proper OOP, but that doesn't mean some APIs would not benefit.Culbreth
So all types are supposed to be "Known" at compile time, effectively making a tightly knit group of types. That's what packages used to be for.Ectogenous
F
33

The JEP 409 explains it as

A sealed class or interface can be extended or implemented only by those classes and interfaces permitted to do so.

A more practical explanation is the following:

The situation in the past was:

  • You could not restrict an interface being extended by another interface
  • You could not constraint which classes where able to implement a specific interface.
  • You had to declare a class as final in order to not be extended by another class. This way no class could extend the declared final class. This was black or white approach.

The current situation with sealed keyword is:

  • You can now restrict an interface being extended by other interfaces and make a rule for only some specific interfaces which will be allowed to extend it.

    Example:

    public sealed interface MotherInterface permits ChildInterfacePermitted {}
    
    //Has to be declared either as sealed or non-sealed
    public non-sealed interface ChildInterfacePermitted extends MotherInterface {}  
    
    public interface AnotherChildInterface extends MotherInterface {} 
    //compiler error! It is not included in the permits of mother inteface
    
  • You can now create an interface and select only specific classes that are allowed to implement that interface. All other classes are not allowed to implement it.

    Example:

     public sealed interface MotherInterface permits ImplementationClass1 {} 
    
     //Has to be declared either as final or as sealed or as non-sealed
     public final class ImplementationClass1 implements MotherInterface {} 
    
     public class ImplementationClass2 implements MotherInterface {} 
     //compiler error! It is not included in the permits of mother inteface
    
  • You can now restrict a class being extended (same as before with final) but you can now allow some specific classes to extend it. So now you have more control as before the keyword final was absolute restricting every class from extending the declared final class

    Example:

    public sealed class MotherClass permits ChildClass1 {}
    
    //Has to be declared either as final or as sealed or as non-sealed
    public non-sealed class ChildClass1 extends MotherClass {} 
    
     public class ChildClass2 extends MotherClass {} 
     //compiler error! It is not included in the permits of MotherClass
    

Important notes:

  • The sealed class and its permitted subclasses must belong to the same module, and, if declared in an unnamed module, to the same package.

    Example:

    Let's say that we have the same unnamed module and the following packages

      -packageA
         -Implementationclass1.java
      -packageB
         -MotherClass.java
    

    or

       -root
          -MotherClass.java
          -packageA
             -Implementationclass1.java
    

    You will get the error Class is not allowed to extend sealed class from another package. So if you have an unnamed module all participating classes and interfaces for the sealed function must be placed exactly on the same package.

  • Every permitted subclass must directly extend the sealed class.

Formation answered 18/9, 2021 at 11:41 Comment(0)
P
6

Sealed classes

A sealed class is a constraint that permits only given classes to implement it. These permitted classes must explicitly extend the sealed class and also have one of the sealed, non-sealed, or final modifiers. The feature is delivered as of java 17 (JEP 409) and was available as a preview longer before (Java 15).

sealed interface IdentificationDocument permits IdCard, Passport, DrivingLicence { }
final class IdCard implements IdentificationDocument { }
final class Passport implements IdentificationDocument { }
non-sealed class DrivingLicence implements IdentificationDocument { }
class InternationalDrivingPermit extends DrivingLicence {}

Usage with pattern matching

I find this new feature awesome in conjunction with pattern matching introduced as a preview as of Java 17 (JEP 406)!

The permitted class restriction assures all the subclasses are known in compile time. Using the switch expressions (JEP 361 as of Java 14) the compiler requires either to list all the permitted classes or use the default keyword for the remaining ones. Consider the following example using the classes above:

final String code = switch(identificationDocument) {
    case IdCard idCard -> "I";
    case Passport passport -> "P";
};

The compiler upon javac Application.java --enable-preview -source 17 results in an error:

Application.java:9: error: the switch expression does not cover all possible input values
                final String code = switch(identificationDocument) {
                                    ^
Note: Application.java uses preview features of Java SE 17.
Note: Recompile with -Xlint:preview for details.
1 error

Once all permitted classes or the default keyword are used, the compilation is successful:

final String code = switch(identificationDocument) {
    case IdCard idCard -> "I";
    case Passport passport -> "P";
    case DrivingLicence drivingLicence -> "D";
};
Parham answered 12/8, 2022 at 5:31 Comment(0)
P
5

Sealed classes is an addition to the Java language giving a class author a fine-grained control over which classes can extend it. Before, you could either allow everyone to inherit your class or disallow it completely (using "final"). It also works for interfaces.

Additionally, it's a prerequisite for the pattern matching feature because all descendants are known during compilation.

As usual, there is a downside - sealed classes and interfaces can't be mocked / faked which is a testing impediment.

Prima answered 7/10, 2022 at 10:29 Comment(0)
I
2

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.

Ithaca answered 1/1, 2024 at 16:1 Comment(0)
G
1

As per this documentation, Sealed classes and interfaces restrict which other classes or interfaces may extend or implement them. It is more of a declarative way to restrict the use of a superclass rather than using access modifiers.

In Java, a class can be final so no other classes can subclass it. If a class is not final, then it is open to all other classes to support code reusability. Doing so would raise data modeling concerns.

The below NumberSystem class is open to all classes, so any subclass can extend it. What if you want to restrict this NumberSystem to a fixed set of subclasses (Binary, Decimal, Octal, and HexaDecimal)?. It means you don’t want any other arbitrary class to extend this NumberSystem class.

class NumberSystem { ... }
final class Binary extends NumberSystem { ... }
final class Decimal extends NumberSystem { ... }
final class Octal extends NumberSystem { ... }
final class HexaDecimal extends NumberSystem { ... }

Using sealed class, you can achieve it by controlling the subclasses that can extend it and prevent any other arbitrary class from doing so.

Gastronome answered 6/11, 2021 at 2:38 Comment(0)
L
1

What Are Sealed Classes in Java?

The final modifier can be considered a strong form of sealing, where extension/implementation is prohibited completely.

Conceptually: final = sealed + an empty permits clause.

Unlike final which completely prohibits extension/implementation. A Sealed class/interface restrict which other classes or interfaces may extend or implement them.


History

  1. Sealed Classes were proposed by JEP 360 and delivered in JDK 15 as a preview feature.
  2. They were proposed again, with refinements, by JEP 397 and delivered in JDK 16 as a preview feature.
  3. This JEP proposes to finalize Sealed Classes in JDK 17, with no changes from JDK 16.

Goals

  • Allow the author of a class or interface to control which code is responsible for implementing it.

  • Provide a more declarative way than access modifiers to restrict the use of a superclass.

Description

  1. A class/interface is sealed by applying the sealed modifier to its declaration.

  2. Then, after any extends and implements clauses, the permits clause specifies the classes that are permitted to extend the sealed class.

    Example

    For example, the following declaration of Loan specifies permitted UnsecuredLoan, SecuredLoan subclasses:

    sealed interface Loan permits UnsecuredLoan,SecuredLoan{}
    
    final class UnsecuredLoan implements Loan {}
    
    record SecuredLoan() implements Loan{}
    

Benefits Of Sealed Class With Pattern Matching

Using Pattern Matching, instead of inspecting an instance of a sealed class with if-else chains, we can use a switch enhanced with type test patterns.

This will allow the Java compiler to check all the permitted classes are covered for us.

For example, consider this code:

void checkLoanType(Loan loan) {
    if (loan instanceof UnsecuredLoan unsecuredLoan) {
//    something
    } else if (loan instanceof SecuredLoan securedLoan) {
//     something
    }
}

The Java compiler cannot ensure that the instanceof tests cover all the permitted subclasses of Loan. Thus, no compile-time error message would be issued if any instanceof Loan was omitted.

In contrast, using a pattern matching switch expression, the compiler can confirm that every permitted subclass of Loan is covered. The compiler will, moreover, issue an error message if any of the cases are missing:

void checkLoanType(Loan loan) {
     switch (loan) { 
       case SecuredLoan securedLoan -> {} //generated by compiler.
       case UnsecuredLoan unsecuredLoan -> {} //generated by compiler.
     }
}

Reference: JEP 360: Sealed Classes

Lucas answered 26/11, 2023 at 16:27 Comment(0)
H
0

All sealed java classes or interfaces must use permits keyword. For example:

Parent.class:

public sealed class Parent permits Child1, Child2 {
  void parentMethod() {
    System.out.println("from a sealed parent class ");
  }
}

Child1.java:

public final class Child1 extends Parent {
  public static void main(String[] args) {
    Child1 obj = new Child1();
    obj.parentMethod();
  }
}

Child2.java:

public final class Child2 extends Parent {
  public static void main(String[] args) {
    Child2 obj = new Child2();
    obj.parentMethod();
  }
}

Child3.java

public final class Child3 extends Parent {
  public static void main(String[] args) {
    Child3 obj = new Child3();
    obj.parentMethod();
  }
}

This Child3 class code will throw a compile-time error saying The type Child3 extending a sealed class Parent should be a permitted subtype of Parent (permits Child3, just like Child1 and Child2).

Hussite answered 18/1, 2022 at 15:48 Comment(1)
Sealed classes without the permits keyword compile just fine, so it seems that it is not actually required. From my testing, it appears that when the permits keyword is omitted, only nested classes are permitted to extend the sealed class. (I'd assume this applies to interfaces too, but I haven't tested)Delozier
C
0

Sealed Classes: Sealed classes are a feature that allows developers to control the extent to which other classes can inherit from them. By declaring a class as sealed, you can specify which other classes are allowed to subclass it. This feature enhances encapsulation and provides more control over class hierarchies and extensions. https://java-speed.blogspot.com/2023/07/what-is-sealed-classes-in-java-17.html

Cnidoblast answered 10/7, 2023 at 14:5 Comment(1)
W
0

Sealed classes are a new feature in Java that provide more control over class hierarchies and help enforce stronger encapsulation. By declaring a class as sealed, developers can specify which classes are allowed to extend it. This effectively restricts the subclasses to a predefined set, thereby preventing unauthorized extensions. Sealed classes are declared using the sealed modifier followed by the permitted subclasses within curly braces.

Syntax

public sealed class MySealedClass permits Subclass1, Subclass2, Subclass3 {
// Class members and methods
}

Note: Any attempt to create a subclass outside of this permitted set will result in a compilation error java: invalid permits clause.

Benefits of Sealed Classes

1.Enhanced Readability

2.Stronger Encapsulation

3.Improved Maintainability

Wormwood answered 8/2, 2024 at 16:3 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.