Java technology is known for its regular and frequent releases, with something new developers can look forward to each time. In September 2020, it introduced another one of these exciting new Java features. The release of Java SE 15 included “sealed classes” (JEP 360) as a preview feature. It is a prominent Java feature. This is because the introduction of sealed classes in Java holds the solution to a problem Java has had from its initial 1.0 version, released 25 years ago.
Table of Contents
See Also: Looking Back On 25 Years Of Java: Major Milestones
As we all know, one of the fundamentals of object-oriented programming is inheritance. Java programmers have been reusing fields and methods of existing classes by inheriting them to new classes. It saves time and they don’t have to write the code again. But things change if a developer does not want to allow any random class to extend his/her created class. This developer can seal a class if he/she doesn’t want any clients of his/her library declaring any more primitives. By sealing a class, Java Developers can now specify which classes are allowed to extend, restricting any other arbitrary class from doing so.
Sealed classes in Java primarily provide restrictions in extending subclasses. A sealed class is abstract by itself. It cannot be instantiated directly. But it can have abstract members. Restriction is not a new concept in Java. We are all aware of a Java feature called “final classes.” It has already been offering restricting extension but the addition of sealed classes in Java can be considered a generalization of finality. Restriction primarily offers two advantages:
To seal a class, we need to add the sealed modifier to its declaration. After that, a permits clause is added. This clause specifies the classes that are allowed to be extended. It must be added after any extends and implements clauses.
Below is a very simple demonstration of a sealed class in Java.
1. public sealed class Vehicle permits Car, Truck, Motorcycle {...} 2. final class Car extends Vehicle {...} 3. final class Boat extends Vehicle {...} 4. final class Plane extends Vehicle {...}
In the example above, ‘Vehicle’ is the name of a sealed class, which specifies three permitted subclasses; Car, Boat and Plane.
There are certain conditions for declaring a sealed class that must be fulfilled by the subclasses:
The permits list is defined to mention the selected classes that can implement ‘Vehicle’. In this case, these classes are ‘Car’, ‘Boat’ and ‘Plane’. A compilation error will be received if any other class or interface attempts to extend the ‘Vehicle’ class.
Sealed classes offer a new way to declare all available subclasses of a class or interface. It’s a handy Java feature. Especially if a developer wishes to make superclasses accessible while restricting unintended extensibility. It also allows classes and interfaces to have more control over their permitted subtypes. This can be useful for many applications including general domain modelling and for building more secure and stable platform libraries.
Another noticeable advantage of introducing sealed classes in Java is the simplification of code. It greatly simplifies code by providing an option to represent the constraints of the domain. Now Java coders are not required to use a default section in a switch or a catch-all ‘else’ block to avoid getting an unknown type. Additionally, it also seems useful for serialization of data to and from structured data formats, like XML. Sealed classes also allow developers to know all possible subtypes that are supported in the given format. (And yes, the subtypes are not hidden. This will be discussed in detail later in the article.)
Sealed classes can also be used as a layer of additional protection against initialization of unintended classes during polymorphic deserialization. Polymorphic deserialization has been one of the primary sources of attacks in such frameworks. These frameworks can take advantage of the information of the complete set of subtypes, and in case of a potential attack, they can stop before even trying to load the class.
The typical process of creating a new class or interface includes deciding which scope modifier must be used. It is usually very simple until the developers come across a project where using a default scope modifier is not recommended by the official style guide. With sealed classes, developers now get better accessibility, using inheritance with a sealed scope modifier while creating new classes. In other words, with the entry of sealed classes in Java, if a developer needs the super class to be widely accessible but not arbitrary extensible, he/she has a straightforward solution.
Sealed classes also allow Java libraries’ authors to decouple accessibility from extensibility. It provides freedom and flexibility to developers. But they must use it logically and not overuse it. For instance, ‘List’ cannot be sealed, as users should have the ability to create new kinds of ‘Lists’. It makes sense for developers to not seal it.
Sealed classes also carry out an exhaustive list of possible subtypes, which can be used by both programmers and compilers. For example, in the defined class above, a compiler can extensively reason about the vehicles class (not possible without this list). This information can be used by some other tools as well. For instance, the Javadoc tool lists the permitted subtypes in the generated documentation page for a sealed class.
Sealed classes rank highly among the other features released in Java 15. Jonathan Harley, a Software Development Team Leader provided an excellent explanation on Quora about how important sealed classes can be for Java Developers: “The fact that interfaces, as well as classes, can be sealed is important to understand how developers can use them to improve their code. Until now, if you wanted to expose an abstraction to the rest of an application while keeping the implementation private, your only choices were to expose an interface (which can always be extended) or an abstract class with a package-private constructor which you hope will indicate to users that they should not instantiate it themselves. But there was no way to restrict a user from adding their constructors with different signatures or adding their package with the same name as yours.”
He further explained, “Sealed types allow you to expose a type (interface or class) to other code while still keeping full control of subtypes, and they also allow you to keep abstract classes completely private…”
The example mentioned earlier in the article makes a statement about how a ‘Vehicle’ can only either be a:
It means that the set of all Vehicles is equal to the set of all Cars, all Boats and all Planes combined. This is why sealed classes are also known as “sum types.” Because their value set is the sum of the value sets of a fixed list of other types. Sum types, and sealed classes are new for Java but not in the larger scale of things.Scala and many other high-level programming languages have been using sealed classes too, as well as sum types for quite some time.
Object-oriented modelling has always encouraged developers to keep the implementation of an abstract type hidden.
But then why is this new Java feature contradicting this rule?
When developers are modelling a well-understood and stable domain, encapsulation can be neglected because users will not be benefitting from the application of encapsulation in this case. In the worst possible scenario, it may even make it difficult for clients to work with a very simple domain.
This does not mean that encapsulation is a mistake. It just means that at a higher and complex level of programming, developers are aware of the consequences. So they can make the call to go a bit out of line to get some work done.
Sealed classes work well with records (JEP 384). Record is a relatively new Java feature that is a form of product type. Records are a new kind of type declaration in Java similar to Enum. It is a restricted form of class. Records are implicitly final, so a sealed hierarchy with records is slightly more concise. To explain this further, we can extend the previously mentioned example using records to declare the subtypes:
1. sealed interface Vehicle permits Car, Boat, Plane { 2. record Car (float speed, string mode) implements Vehicle {...} 3. record Boat (float speed, string mode) implements Vehicle {...} 4. record Plane (float speed, string mode) implements Vehicle {...} }
This example shows how sum and record (product types) work together; we can say that a car, a plane or a boat is defined by its speed and its mode.
In another application, it can also be used for selecting which other types can be the subclasses of the sealed class.
For example, simple arithmetic expressions with records and sealed types would be like the code mentioned below:
1. sealed interface Arithmetic {...} 2. record MakeConstant (int i) implements Arithmetic {...} 3. record Addition (Arithmetic a, Arithmetic b) implements Arithmetic {...} 4. record Multiplication (Arithmetic a, Arithmetic b) implements Arithmetic {...} 5. record Negative (Arithmetic e) implements Arithmetic {...}
Here we have two concrete types: addition and multiplication which hold two subexpressions, and two concrete types, MakeConstant and Negative which holds one subexpression. It also declares a supertype for arithmetic and captures the constraint that these are the only subtypes of arithmetic.
The combination of sealed classes and records is also known as “algebraic data types”. Records allow us to express product types, and sealed classes allow us to express sum types.
Both records and sealed types have an association with pattern matching. Records admit easy decomposition into their components, and sealed types provide the compiler with exhaustiveness information so that a switch that covers all the subtypes need not provide a default clause.
A limited form of pattern matching has been previously introduced in Java SE 14 which will hopefully be extended in the future. This initial version of Java feature allows Java developers to usetype patternsin “instanceof.”
For example, let’s take a look at the code snippet below:
1. if (vehicle instanceof Car c) { 2. // compiler has itself cast vehicle to the car and bound it to c 3. System.out.printf("The speed of this car is %d%n", c.speed()); }
Although many other tools were released with sealed classes, it remains the most prominent Java feature of the release. We are still not certain about the final representation of sealed classes in Java. (It has been released as a preview in Java 15). But so far sealed classes are offering a wide range of uses and advantages. They prove to be useful as a domain modelling technique, when developers need to capture an exhaustive set of alternatives in the domain model. Sealed types become a natural complement to records, as together they form common patterns. Both of them would also be a natural fit for pattern matching. It is obvious that sealed classes serve as a quite useful improvement in Java. And, with the overwhelming response from the Java community, we can expect that a better, more refined version of sealed classes in Java will soon be released
Shaharyar Lalani is a developer with a strong interest in business analysis, project management, and UX design. He writes and teaches extensively on themes current in the world of web and app development, especially in Java technology.
Create a free profile and find your next great opportunity.
Sign up and find a perfect match for your team.
Xperti vets skilled professionals with its unique talent-matching process.
Connect and engage with technology enthusiasts.
© Xperti.io All Rights Reserved
Privacy
Terms of use