Switch expressions + pattern matching for switch + sealed, working together
A sealed interface declares its complete, closed set of permitted implementations. Combine that with
pattern-matching switch over the sealed type, and the compiler can verify exhaustiveness -- no
default branch needed, and adding a new permitted subtype without updating the switch is a compile error,
not a runtime surprise. Record patterns let a case deconstruct a record's components directly, with an
optional when guard.
(The sealed interface and its records are declared together inside one wrapper class below -- purely a
notebook/JShell mechanic: JShell evaluates each top-level declaration as its own incremental unit and can't
resolve the circular permits/implements reference across separate snippets the way a real .java file
compiling everything together can. Nesting them inside one class makes the whole hierarchy a single unit
again. This has no bearing on the language feature itself -- in a real source file you'd write these as
ordinary top-level or sibling types.)
class Shapes {
sealed interface Shape permits Circle, Square, Rectangle {}
record Circle(double radius) implements Shape {}
record Square(double side) implements Shape {}
record Rectangle(double w, double h) implements Shape {}
static double area(Shape s) {
return switch (s) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Square sq -> sq.side() * sq.side();
case Rectangle r -> r.w() * r.h();
// no default -- the compiler knows Circle/Square/Rectangle are the ONLY permitted shapes
};
}
}
for (Shapes.Shape shape : List.of(new Shapes.Circle(2), new Shapes.Square(3), new Shapes.Rectangle(2, 5))) {
System.out.printf("%s area = %.2f%n", shape.getClass().getSimpleName(), Shapes.area(shape));
}
Record patterns deconstruct components directly in the case, and when adds a guard condition:
Shapes.Shape s = new Shapes.Rectangle(4, 5);
String desc = switch (s) {
case Shapes.Rectangle(double w, double h) when w == h -> "square-shaped rectangle";
case Shapes.Rectangle(double w, double h) -> "rectangle " + w + "x" + h;
case Shapes.Circle(double r) -> "circle r=" + r;
case Shapes.Square(double side) -> "square " + side;
};
System.out.println(desc);
Removing a case leaves the switch non-exhaustive -- a real compile error, reproduced verbatim (two-shape sealed interface, switch only handles one):
SealedFail.java:7: error: the switch expression does not cover all possible input values
return switch (s) {
^
1 error