Sealed Interfaces with Pattern Matching
Sealed interfaces (Java 17+) plus switch pattern matching (Java 21+) give Java algebraic-data-type ergonomics: a closed family of subtypes that the compiler can check exhaustively in one switch. This snippet shows the modern syntax in comments and a Java 13-compatible equivalent using a visitor-style abstract class hierarchy that compiles in our test runner. Use this pattern for tagged unions like `Result<Ok, Err>`, AST nodes, and state machines.
1,112 views
24
// Java 17+ syntax (cannot run on JDK 13):
//
// public sealed interface Shape permits Circle, Square, Triangle {}
// public record Circle(double r) implements Shape {}
// public record Square(double side) implements Shape {}
// public record Triangle(double base, double height) implements Shape {}
//
// Below is the JDK 13 equivalent: an abstract class with package-private
// constructor and a fixed set of final subclasses.
abstract class Shape {
private Shape() {} // private ctor + final subclasses == effectively sealed
static final class Circle extends Shape { final double r;
Circle(double r) { this.r = r; } }
static final class Square extends Shape { final double side;
Square(double s) { this.side = s; } }
static final class Triangle extends Shape { final double base, height;
Triangle(double b, double h) { this.base = b; this.height = h; } }
}
public class Main {
public static void main(String[] args) {
Shape[] shapes = {
new Shape.Circle(2),
new Shape.Square(3),
new Shape.Triangle(4, 5)
};
for (Shape s : shapes) System.out.printf("area=%.2f%n", area(s));
}
static double area(Shape s) {
// Java 21+ pattern-matching switch (cannot run on JDK 13):
//
// return switch (s) {
// case Circle c -> Math.PI * c.r() * c.r();
// case Square sq -> sq.side() * sq.side();
// case Triangle t -> 0.5 * t.base() * t.height();
// };
//
// JDK 13 equivalent uses instanceof + cast:
if (s instanceof Shape.Circle) {
Shape.Circle c = (Shape.Circle) s;
return Math.PI * c.r * c.r;
}
if (s instanceof Shape.Square) {
Shape.Square sq = (Shape.Square) s;
return sq.side * sq.side;
}
Shape.Triangle t = (Shape.Triangle) s;
return 0.5 * t.base * t.height;
}
}A sealed interface declares a closed set of allowed implementers via the permits clause; once published, no other class can extend it without modifying the source. That promise is what enables exhaustive checking: the Java 21 compiler verifies that a switch over a sealed type covers every permitted subtype and refuses to compile otherwise. The runtime trick that simulates sealing on older JDKs is a private constructor plus final static nested subclasses, which prevents external extension while still allowing the type to act as a discriminator. The pattern is the foundation for tagged unions and is the cleanest way to model finite alternatives like Result<T, E> or a small state machine.
2 more snippets in this entry are available for premium members.
Upgrade to Premium