Before Java 17:
Without sealed classes, any class could extend another class if it wasnβt declared as final
, leading to potentially unpredictable subclassing.
public abstract class Shape { // Class body omitted for brevity public abstract double calcArea(); } public class Circle extends Shape { // Circle implementation } public class Triangle extends Shape { // Triangle implementation } public class Square extends Shape { // Square implementation } public class FooBarShape extends Shape { @Override public double calcArea() { return -Math.sqrt(2); // π© Problematic uncontrolled subclass with potential for bugs and inconsistencies } }
After Java 17:
With sealed classes you can strictly specify your hierarchy from the superclass.
public sealed abstract class Shape permits Circle, Square, Triangle { // Class body omitted for brevity public abstract double calcArea(); } public final class Circle extends Shape { // Circle implementation } public final class Triangle extends Shape { // Triangle implementation } public final class Square extends Shape { // Square implementation } public class FooBarShape extends Shape { // β 'FooBarShape' is not allowed in the sealed hierarchy // Problematic implementation avoided β }
Shape
is a sealed class that explicitly permits Circle
, Triangle
and Square
as its direct subclasses.Circle
, Triangle
and Square
are all final
classes, meaning they cannot be subclassed further.FooBarShape
, having not been explicitly permitted by the sealed class Shape
, causes a compilation error as it’s trying to inherit a sealed class without permission.Making a sealed class involves adding some keywords to your normal class/record declaration:
sealed
.permits
followed by a comma-separated list of subtypes.public sealed abstract class Shape permits Circle, Square, Triangle {
What is the primary purpose of introducing sealed classes in Java?
A) To allow classes to be sealed against modification
B) To restrict which other classes or interfaces may extend or implement them
C) To automatically seal all methods within a class
D) To integrate better with Java’s reflection APIs
______ sealed class Vehicle ______ Car, Truck { } ______ class Car extends Vehicle { } final class Truck extends Vehicle { }
Below is an attempt to define a sealed class hierarchy in Java. However, the code contains several mistakes. Identify and correct them so that the code compiles successfully.
public sealed interface Animal permits Cat, Dog { void makeSound(); } class Cat extends Animal { // Error here public void makeSound() { System.out.println("Meow"); } } class Dog implements Animal { // Error here public void makeSound() { System.out.println("Bark"); } }
Vehicle
-class that contains some basic fields (e.g. colour
, movementSpeed
, etc.) and abstract method (e.g. move()
).Car
(which has an extra breakHorsePower
field) and Bicycle
(with a nrOfGears
field), each of which provides its own implementation of move()
.Vehicle
to just those two subclasses you just made.final
so that it can still compile.Airplane
, that also inherits Vehicle
. What happens? Why?VehiclesApp
-class, in the main
-method, make a list of 3 vehicles (polymorphism).Answer: B) To restrict which other classes or interfaces may extend or implement them
______ sealed class Vehicle ______ Car, Truck { } ______ class Car extends Vehicle { } final class Truck extends Vehicle { }
public sealed class Vehicle permits Car, Truck { } final class Car extends Vehicle { } final class Truck extends Vehicle { }
public sealed interface Animal permits Cat, Dog { void makeSound(); } class Cat extends Animal { // Error here public void makeSound() { System.out.println("Meow"); } } class Dog implements Animal { // Error here public void makeSound() { System.out.println("Bark"); } }
public sealed interface Animal permits Cat, Dog { void makeSound(); } final class Cat implements Animal { // Corrected: 'implements' used, and class marked as 'final' public void makeSound() { System.out.println("Meow"); } } final class Dog implements Animal { // Corrected: class marked as 'final' public void makeSound() { System.out.println("Bark"); } }
implements
should be used instead of extends
for interfaces.Cat
-class and Dog
-class must be declared with final
or another subclass-specific modifier to align with the sealed contract unless it is intended to be non-sealed
, but then it would need to be explicitly stated.public abstract sealed class Vehicle permits Car, Bicycle { private String colour; private double movementSpeed; public Vehicle(String colour) { this.colour = colour; } public String getColour() { return colour; } public void setColour(String colour) { this.colour = colour; } public double getMovementSpeed() { return movementSpeed; } public void setMovementSpeed(double movementSpeed) { this.movementSpeed = movementSpeed; } public abstract void move(); }
public final class Car extends Vehicle { private double breakHorsePower; public Car(String colour, double breakHorsePower) { super(colour); this.breakHorsePower = breakHorsePower; } @Override public void move() { System.out.printf("The %s car is moving at %f km/h with a break horse power of %f kW%n", getColour(), getMovementSpeed(), breakHorsePower); } }
public final class Bicycle extends Vehicle { private int nrOfGears; public Bicycle(String colour, int nrOfGears) { super(colour); this.nrOfGears = nrOfGears; } public int getNrOfGears() { return nrOfGears; } public void setNrOfGears(int nrOfGears) { this.nrOfGears = nrOfGears; } @Override public void move() { System.out.printf("The %s bike is moving at %f km/h%n", getColour(), getMovementSpeed()); } }
public class Airplane extends Vehicle { // β 'Airplane' is not allowed in the sealed hierarchy // Class body omitted for brevity }
public class VehiclesApp { public static void main(String[] args) { List<Vehicle> vehicles = List.of( new Car("red", 100.0), new Bicycle("blue", 2), new Car("green", 120.0), new Bicycle("yellow", 4) ); } }
non-sealed
Keywordnon-sealed
keyword.public sealed class Shape permits Circle, Rectangle { // Common methods for shapes } final class Circle extends Shape { // Circle-specific methods; no further subclassing allowed } non-sealed class Rectangle extends Shape { // Rectangle-specific methods; subclassing is allowed } // This subclassing is permissible because Rectangle is non-sealed class Square extends Rectangle { // Further customization specific to squares }
non-sealed
KeywordCar
-class so that it is no longer final
but still part of the sealed hierarchy.Car
, the ElectricCar
-class and the CombustibleEngineCar
-class.VehiclesApp
to include objects of your 2 new subclasses.public non-sealed class Car extends Vehicle { private double breakHorsePower; public Car(String colour, double breakHorsePower) { super(colour); this.breakHorsePower = breakHorsePower; } @Override public void move() { System.out.printf("The %s car is moving at %f km/h with a break horse power of %f kW%n", getColour(), getMovementSpeed(), breakHorsePower); } }
public class ElectricCar extends Car { public ElectricCar(String colour, double breakHorsePower) { super(colour, breakHorsePower); } }
public class CombustibleEngineCar extends Car { public CombustibleEngineCar(String colour, double breakHorsePower) { super(colour, breakHorsePower); } }
public class VehiclesApp { public static void main(String[] args) { List<Vehicle> vehicles = List.of( new Car("red", 100.0), new Bicycle("blue", 2), new Car("green", 120.0), new Bicycle("yellow", 4), new ElectricCar("purple", 150.0), new CombustibleEngineCar("orange", 200.0) ); } }
Below are some of the most common use cases where sealed classes prove particularly beneficial:
Sealed classes are excellent for domain-driven design where you need to represent a fixed set of closely related types. They help enforce business rules at the compiler level, ensuring that the domain model remains consistent and valid throughout its lifecycle.
Transaction
with specific subclasses like Deposit
, Withdrawal
, and Transfer
. This setup ensures that all transactions processed in the system are one of these defined types, and no unexpected transaction types can exist.public sealed class Transaction permits Deposit, Withdrawal, Transfer { // Common transactional logic here } public final class Deposit extends Transaction { // Specific logic for a deposit } public final class Withdrawal extends Transaction { // Specific logic for a withdrawal } public non-sealed class Transfer extends Transaction { // Specific logic for a transfer, allows further subclassing if needed }
In state machine implementations, sealed classes can define a finite number of states and transitions, which helps in managing state transitions explicitly and safely.
Draft
, Review
, or Published
. Using a sealed class to define these states can ensure that transitions between states are handled cleanly and that all possible states are explicitly known and handled.public sealed class DocumentState permits Draft, Review, Published { // Base methods common to all states } public final class Draft extends DocumentotState { // Methods specific to the draft state } public final class Review extends DocumentState { // Methods specific to the review state } public final class Published extends DocumentState { // Methods specific to the published state }
Starting from Java SE 21, sealed classes are particularly useful with pattern matching, as they allow the compiler to guarantee that all possible subtypes are covered in a switch
expression or statement, eliminating the need for a default case. This leads to safer code that is less prone to errors.
Shape
could have subclasses such as Circle
, Square
, and Rectangle
. Using pattern matching, you can easily and safely compute properties like area or perimeter, with compile-time checking to ensure all types are handled.public static void printShapeDetails(Shape shape) { double area = shape.area(); double perimeter = shape.perimeter(); String details = switch (shape) { case Circle c -> String.format("Circle with radius %.2f", c.radius); case Rectangle r -> String.format("Rectangle with length %.2f and width %.2f", r.length, r.width); case Triangle t -> String.format("Triangle with sides %.2f, %.2f, %.2f", t.a, t.b, t.c); default -> throw new IllegalStateException("Unexpected shape type: " + shape.getClass()); }; System.out.printf("%s has an area of %.2f and a perimeter of %.2f%n", details, area, perimeter); }
When designing APIs, especially libraries or frameworks, sealed classes can be used to restrict how developers interact with the API, guiding them towards correct usage and preventing misuse by limiting subclassing to a known set of classes.
Plugin
that all plugins must extend. Specific types of plugins can be defined as final classes extending Plugin
, ensuring all plugins adhere to a specific contract while preventing further subclassing.public sealed class Plugin permits AudioPlugin, VideoPlugin { // Common plugin functionality } public final class AudioPlugin extends Plugin { // Audio-specific plugin functionality } public final class VideoPlugin extends Plugin { // Video-specific plugin functionality }
Sealed classes enhance security by controlling subclassing, which is crucial in environments where the integrity and consistency of data or behavior are paramount. This can prevent malicious subclassing or inadvertent errors in extending classes in security-sensitive applications.
public sealed class AuthenticationType permits PasswordAuth, TokenAuth { // Common authentication functionality } public final class PasswordAuth extends AuthenticationType { // Password authentication logic } public final class TokenAuth extends AuthenticationType { // Token authentication logic }