Java SE 17: Sealed Classes

The sealed classes and interfaces is the enhancement feature to the JDK. It is very neat feature to achieve restricted inheritance to the classes or interfaces without limiting its visibility. The feature was implemented through the following JEPs 360, 367, 407 starting the 1st preview in Java SE 15 (see java-versions). 

In previous version we get familiar with package-private access modifier to limit inheritance of a particular abstract classes, classes or interfaces. It was not sufficient as user was for example not able to access the class without not implementing it.

The sealed classes motivation is to give a possibility to the super-class to be widely accessible without requirement to extend it. 

The sealed classes give a control which sub-classes or interfaces may implement the super-class or interfaces. 

Let’s explore its usage on example of creating a different types of vehicle with specific engine types.

The Car, Bus vehicle types have StandardEngine and Truck type has SlowEngine type. Also we restrict by using a sealed classes which of sub-classes may implement them. All three vehicle types implementing a Vehicle interface. 

It means that when for example class Motorcycle want to implement an interface Vehicle, the exception is thrown during the compile time.

$ javac --enable-preview --release 16  -g -classpath out/jep397 -sourcepath src/jep397 -d out/jep397 ./src/jep397/SealedClassesMain.java

output: 
src/jep397/Motorcycle.java:1: error: class is not allowed to extend sealed class: Vehicle (as it is not listed in its permits clause)
public class Motorcycle implements Vehicle {
       ^
Note: Some input files use preview language features.
Note: Recompile with -Xlint:preview for details.
1 error
 

The Sealed Classes allows to detect a suspicious code behaviour already at the compile time.

Let’s create an example classes into the separate files as following:

public sealed interface Vehicle permits Car, Bus, Truck {
    void startEngine();
    void stopEngine();
    Integer getMaxSpeed();
}  

public abstract sealed class StandardEngine permits Car, Bus {

    protected boolean running;
    public StandardEngine(){
        this.running = false;
    }

    public void startEngine(){
        System.out.println("StandardEngine, startEngine");
        this.running = true;
    }

    public void stopEngine(){
        System.out.println("StandardEngine, stopEngine");
        this.running = false;
    }
}

public abstract sealed class SlowEngine permits Truck  {
    protected boolean running;
    public SlowEngine(){
        this.running = false;
    }

    public void startEngine(){
        System.out.println("SlowEngine, startEngine");
        this.running = true;
    }

    public void stopEngine(){
        System.out.println("SlowEngine, stopEngine");
        this.running = false;
    }    
}
 

We can see on the abstract classes above how sealed keyword is used during defining a abstract classes (“SlowEngine“, “StandardEngine“) and the interface ( “Vehicle“).  

Let’s create also following classes: 

public final class Bus extends StandardEngine implements Vehicle {
    
    private Integer maxSpeed;

    public Bus (){
        this.maxSpeed = 100;
    }

    public Integer getMaxSpeed(){
        return maxSpeed;
    }

    public String toString(){
        return "Bus{maxSpeed=" + maxSpeed + ", running="+ super.running +'}';
    }
}

public non-sealed class Car extends StandardEngine implements Vehicle {
    
    private Integer maxSpeed;

    public Car (){
        this.maxSpeed = 120;
    }

    public Integer getMaxSpeed(){
        return maxSpeed;
    }

    public String toString(){
        return "Car{maxSpeed=" + maxSpeed + ", running="+ super.running +'}';
    }
}

public final class Truck extends SlowEngine implements Vehicle {
    private Integer maxSpeed;

    public Truck (){
        this.maxSpeed = 80;
    }

    public Integer getMaxSpeed(){
        return maxSpeed;
    }

    public String toString(){
        return "Truck{maxSpeed=" + maxSpeed + ", running="+ super.running +'}';
    }
}
 

The classes that extends the sealed classes or implements the sealed interfaces must have a modifier “final” or “non-sealed” or “sealed”. In case we do not define the one of those modifiers we receive an error at the compile time:

$ javac --enable-preview --release 16  -g -classpath out/jep397 -sourcepath src/jep397 -d out/jep397 ./src/jep397/SealedClassesMain.java

output:
src/jep397/Truck.java:1: error: sealed, non-sealed or final modifiers expected
public class Truck extends SlowEngine implements Vehicle {
       ^
Note: Some input files use preview language features.
Note: Recompile with -Xlint:preview for details.
1 error 

In case the sealed modifier is used for the class Truck then the class Truck must have a sub-classes otherwise an error is thrown at the compile time

$ javac --enable-preview --release 16  -g -classpath out/jep397 -sourcepath src/jep397 -d out/jep397 ./src/jep397/SealedClassesMain.java

output:
src/jep397/Truck.java:1: error: sealed class must have subclasses
public sealed class Truck extends SlowEngine implements Vehicle {
              ^
Note: Some input files use preview language features.
Note: Recompile with -Xlint:preview for details.
1 error 

To compile the sources above use following command when you are running Java SE 16:

# Compile sources
$ javac --enable-preview --release 16  -g -classpath out/jep397 -sourcepath src/jep397 -d out/jep397 ./src/jep397/SealedClassesMain.java

# Run example application
$ java --enable-preview -cp out/jep397 SealedClassesMain

 

Conclusion

The sealed classes opening a new dimension of code reusability and reliability without limiting a visibility. Although some positive facts there are some constraints that are required to keep in mind: 

  1. Each permitted sub-class must defined a modifier: final, sealed or non-sealed (example above)
  2. Each permitted classes must reside in the same module as the sealed class
  3. Each permitted sub-class must extend the sealed class

Happy Sealing and Coding!

Main: Java tutorials