Java Pattern Matching, Sealed Classes and Records
Modern Java (17, 21 and 25) has features which make code shorter and safer. The three most important work together: sealed types, records and pattern matching.
Sealed classes and interfaces
A sealed type says exactly which classes are allowed to extend or implement it. So the compiler knows all the possible cases.
sealed interface Shape permits Circle, Rectangle, Triangle { }
record Circle(double radius) implements Shape { }
record Rectangle(double width, double height) implements Shape { }
record Triangle(double base, double height) implements Shape { }
public class Main {
static double area(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
case Triangle t -> 0.5 * t.base() * t.height();
}; // no default needed
}
public static void main(String[] args) {
System.out.printf("%.2f%n", area(new Circle(2)));
System.out.printf("%.2f%n", area(new Rectangle(3, 4)));
System.out.printf("%.2f%n", area(new Triangle(6, 5)));
}
}
12.57
12.00
15.00
Because Shape is sealed, the switch covers all the cases and needs no default. If you add a new shape and forget the switch, the compiler gives an error.
Pattern matching for switch
A switch can test the type of an object and use a guard (when):
public class Pattern {
static String describe(Object o) {
return switch (o) {
case null -> "nothing";
case Integer i when i > 100 -> "big number " + i;
case Integer i -> "number " + i;
case String s when s.isEmpty() -> "empty text";
case String s -> "text of length " + s.length();
case int[] a -> "int array of " + a.length;
default -> "something else: " + o;
};
}
public static void main(String[] args) {
System.out.println(describe(5));
System.out.println(describe(500));
System.out.println(describe(""));
System.out.println(describe("hello"));
System.out.println(describe(new int[3]));
System.out.println(describe(null));
System.out.println(describe(2.5));
}
}
number 5
big number 500
empty text
text of length 5
int array of 3
nothing
something else: 2.5
Record patterns
A pattern can take a record apart and give you its parts at once:
record Point(int x, int y) { }
record Line(Point from, Point to) { }
public class Rec {
static String show(Object o) {
if (o instanceof Point(int x, int y)) {
return "Point at " + x + "," + y;
}
if (o instanceof Line(Point(var x1, var y1), Point(var x2, var y2))) {
return "Line from " + x1 + "," + y1 + " to " + x2 + "," + y2;
}
return "unknown";
}
public static void main(String[] args) {
System.out.println(show(new Point(1, 2)));
System.out.println(show(new Line(new Point(0, 0), new Point(5, 5))));
System.out.println(show("text"));
}
}
Point at 1,2
Line from 0,0 to 5,5
unknown
A small expression calculator
This is a classic use: a sealed interface, records and a recursive switch.
sealed interface Expr permits Num, Add, Mul { }
record Num(int value) implements Expr { }
record Add(Expr left, Expr right) implements Expr { }
record Mul(Expr left, Expr right) implements Expr { }
public class Calc {
static int eval(Expr e) {
return switch (e) {
case Num n -> n.value();
case Add(Expr l, Expr r) -> eval(l) + eval(r);
case Mul(Expr l, Expr r) -> eval(l) * eval(r);
};
}
public static void main(String[] args) {
Expr e = new Add(new Num(2), new Mul(new Num(3), new Num(4))); // 2 + 3 * 4
System.out.println(eval(e));
}
}
14
What each feature gives
| Feature | Since | Benefit |
|---|---|---|
| Records | Java 16 | Short data classes |
| Pattern matching for instanceof | Java 16 | No manual cast |
| Sealed classes | Java 17 | A closed list of subtypes |
| Pattern matching for switch | Java 21 | Type tests with guards, no long if chains |
| Record patterns | Java 21 | Take a record apart in one step |
| Exercises & Assignments |
|---|
| No Content Found. |
| Interview Questions & Answers |
|---|
| No Content Found. |