Wednesday, March 17, 2021

Sealed classes enhancement in Java 16

Sealed classes remains as preview feature in Java 16, which was released yesterday (16-Mar-2021). 

Only enhancement to the sealed classes is that - Java compiler is enhanced to identify disjoint types by running through the sealed classes hierarchy. Compilation now throws an error when there is casting between disjoint types. 

Lets explore this through an example. 

Consider the below code fragment:

//Main.java
interface Pet {

}

interface Robot {

}

public class Main {
    
    public static void check(Robot r) {
        Pet p = (Pet) r;
    }
    
    public static void main(String[] args) {
        
		check(new Robot() {
		}); // Throws ClassCastException at runtime

		class RoboDog implements Robot, Pet {
		} // Class that implements both Robot and Pet

		check(new RoboDog()); // Valid

    }
}

Here we have two interface types Pet & Robot which are unrelated. 

Compiler does not flag any error and casting Robot type to Pet is allowed. 

This is because, we could have a class that implements both these interfaces and it is valid to cast an object of that class to Pet at runtime. 

The local class RoboDog in the above code does exactly that - implements both the interfaces Robot & Pet and its perfectly valid to cast an instance of RoboDog to Pet type. 

But there are instances where the compiler can statically establish that the two types are disjoint (can never be related) and throw a compilation error when casting is performed between such types. 

To understand how sealed classes helps the compiler to establish two types as disjoint, consider the below code:

//Main1.java
interface Pet {

}

interface Robot {

}

final class RoboDog implements Robot {

}

public class Main {

    public static void check(Robot r) {
        Pet p = (Pet) r;
    }

    public static void main(String[] args) {

        check(new RoboDog()); // Throws ClassCastException at runtime

    }
}

Here, though the RoboDog  

  • implements only Robot and not Pet 
  • and is final and cannot be extended any further

It is very much possible to have a type at runtime that implements both Pet and Robot, and casting Robot type to Pet should be allowed for such scenarios.  

The above code compiles without error, but throws a ClassCastException at runtime because RoboDog is not a Pet type. 

If we make the Robot and RoboDog sealed, then no other types are allowed in the hierarchy. The compiler can establish in this case that Pet & Robot are disjoint (since no other type can be of type Robot anymore) and the compiler throws an error stating that casting of incompatible types is not allowed, where we are casting Robot to Pet.

Below code shows this implementation with sealed classes:

//Main2.java
interface Pet {

}

sealed interface Robot permits RoboDog {

}

final class RoboDog implements Robot {

}

public class Main {

    public static void check(Robot r) {
        Pet p = (Pet) r;
    }

    public static void main(String[] args) {

        check(new RoboDog());

    }
}

Here Robot is sealed allowing only RoboDog subtype. And RoboDog is final, allowing no further subtypes. 

We get a compilation error when this code is compiled on Java 16.

>javac --enable-preview --release 16 Main2.java
Main.java:16: error: incompatible types: Robot cannot be converted to Pet
                Pet p = (Pet) r;
                              ^
Note: Main.java uses preview language features.
Note: Recompile with -Xlint:preview for details.
1 error

This is the only enhancement for sealed classes in Java 16 as compared to its behavior in Java 15. 


Behavior in Java 15:

Compiling the same code on Java 15 does not give a compile time error. 

And since the Robot type hierarchy is fully sealed - not further subtypes of Robot is possible, This code is bound to throw ClassCastException at all times.

Output of compiling the code in Java 15: 

>javac --enable-preview --release 15 Main2.java
Note: Main.java uses preview language features.
Note: Recompile with -Xlint:preview for details.

Code gets compiled successfully on Java 15.


No comments:

Post a Comment