Java tutorials > Modern Java Features > Java 8 and Later > What are sealed classes (Java 17)?

What are sealed classes (Java 17)?

Sealed classes, introduced in Java 17, offer a powerful way to control the inheritance hierarchy of a class or interface. They allow you to explicitly define which classes are permitted to extend or implement a sealed class or interface, preventing any other classes from doing so. This feature enhances code security, maintainability, and readability by restricting the possible subclasses, making reasoning about the code easier.

Basic Syntax and Example

This example defines a sealed class `Shape`. The `permits` keyword specifies that only `Circle`, `Rectangle` and `Square` classes can extend `Shape`. Any other class attempting to extend `Shape` will result in a compile-time error. Notice that subclasses must be `final`, `sealed`, or `non-sealed`. `final` prevents further inheritance, `sealed` continues to restrict inheritance, and `non-sealed` opens the class to unrestricted inheritance (use with caution).

sealed class Shape permits Circle, Rectangle, Square {
    // Common properties and methods for all shapes
}

final class Circle extends Shape {
    // Circle-specific implementation
}

final class Rectangle extends Shape {
    // Rectangle-specific implementation
}

final class Square extends Rectangle{
    // Square-specific implementation
}

Concepts Behind the Snippet

The core concept behind sealed classes is controlled inheritance. Traditional inheritance allows any class to extend another, potentially leading to unexpected behavior or security vulnerabilities. Sealed classes enforce a strict contract by explicitly listing the permitted subclasses. This offers increased type safety, predictability, and better code organization.

Real-Life Use Case Section

Consider a scenario where you're developing an API for handling payment methods. You might want to define a sealed class `PaymentMethod` with permitted subclasses like `CreditCardPayment`, `PayPalPayment`, and `BankTransferPayment`. By sealing the `PaymentMethod` class, you prevent external developers from adding unauthorized payment methods, ensuring the integrity and security of your payment processing system. Another case is in modeling a closed set of states in a state machine. Sealed classes make it easier to ensure that all possible states are handled, improving code robustness.

Best Practices

  • Be Explicit: Clearly define the permitted subclasses using the `permits` clause.
  • Consider Alternatives: Evaluate whether a sealed class is truly necessary. Sometimes, a simple abstract class or interface might suffice.
  • Balance Restriction with Flexibility: While sealed classes provide strong control, avoid overusing them. Consider future extensibility when designing your class hierarchy. If a situation calls for unrestricted inheritance, then `non-sealed` may be appropriate.

Interview Tip

When discussing sealed classes in an interview, emphasize their role in controlling inheritance, improving type safety, and enhancing code maintainability. Be prepared to explain the `permits` clause and the requirements for subclasses of sealed classes (i.e., they must be `final`, `sealed`, or `non-sealed`). Also be ready to discuss potential use cases and the tradeoffs involved in using sealed classes versus other inheritance strategies.

When to Use Them

Use sealed classes when:

  • You want to restrict the possible subclasses of a class or interface.
  • You need to ensure type safety and prevent unexpected behavior.
  • You're working with a closed set of states or options.
  • You want to improve code readability and maintainability by making the inheritance hierarchy explicit.

Memory Footprint

Sealed classes themselves don't inherently introduce significant memory overhead. The memory footprint is primarily determined by the properties and methods defined within the sealed class and its permitted subclasses. The JVM's optimizations can often minimize any potential overhead associated with the sealed nature of the classes.

Alternatives

Before Java 17, you could achieve similar (but less strict) control over inheritance using:

  • Private Constructors: Making the constructor private and providing factory methods. This prevents direct instantiation but doesn't prevent subclassing in the same package.
  • Abstract Classes: Using abstract classes with protected constructors. This restricts instantiation but still allows subclassing.
Sealed classes provide a more robust and explicit solution for controlling inheritance than these older techniques.

Pros

  • Controlled Inheritance: Explicitly defines permitted subclasses, enhancing type safety.
  • Improved Code Readability: Makes the inheritance hierarchy clear and understandable.
  • Enhanced Maintainability: Simplifies reasoning about the code by limiting the number of possible subclasses.
  • Pattern Matching: Works very well with pattern matching, allowing exhaustive matching of all possible subtypes.

Cons

  • Less Flexible: Can make the code less flexible if future extensions are required beyond the permitted subclasses.
  • Requires Java 17 or Later: Limits compatibility with older Java versions.

FAQ

  • What happens if I try to extend a sealed class without being permitted?

    You will get a compile-time error. The compiler will enforce the `permits` clause and prevent any unauthorized classes from extending the sealed class.
  • Can a permitted subclass be abstract?

    Yes, a permitted subclass can be abstract. However, any non-abstract subclass must be `final`, `sealed`, or `non-sealed`.
  • Are sealed classes applicable to interfaces?

    Yes, the `sealed` keyword can also be applied to interfaces. The `permits` clause then specifies the permitted implementing classes.
  • What does `non-sealed` mean?

    When a subclass of a sealed class is declared as `non-sealed`, it removes the inheritance restriction, allowing any other class to extend it. Use it with caution because you lose the control that the sealed class provides.