Pattern Matching for switch - Sip of Java
Billy Korando on November 13, 2023After several preview rounds, pattern matching for switch
made it into JDK 21 as a final feature! Adding pattern matching to switch
will provide developers with many quality-of-life improvements when using switch
. Let’s take a look!
Pattern Matching
Pattern matching was initially added in Java 16 with pattern matching for instanceof
. Pattern matching for instanceof
did significantly improve code readability and maintainability, but still left something to be desired when comparing multiple types, like in this example:
interface Shape{
double area();
}
record Circle(double diameter) implements Shape{...};
record Square(double side) implements Shape{...};
Shape shape = new Circle(10);
String message;
if(shape instanceof Circle c) {
message = "A circle with area of " + c.area();
} else if (shape instanceof Square s) {
message = "A square with area of " + s.area();
} else {
message = "Unknown shape";
}
With Java 21, switch
has been extended to allow case
labels to also be patterns, simplifying the previous example to this:
interface Shape{
double area();
}
record Circle(double diameter) implements Shape{...};
record Square(double side) implements Shape{...};
Shape shape = new Circle(10);
String message = switch(shape){
case Circle c -> "A circle with area of " + c.area();
case Square s -> "A square with area of " + s.area();
default -> "Unknown shape";
};
Guard Conditions
With the update for pattern matching, switch
can support further refinement with case
labels using guard conditions. This can help improve the readability and maintainability of the code by keeping the check conditions within the case label instead of using if
statements within the block of a case
; this example demonstrates how to use a guard condition:
interface Shape{
double area();
}
record Circle(double diameter) implements Shape{...};
record Square(double side) implements Shape{...};
Shape shape = new Circle(10);
String message = switch(shape){
case Circle c when c.area() > 100
-> "A large circle with area of " + c.area();
case Circle c -> "A circle with area of " + c.area();
case Square s -> "A square with area of " + s.area();
default -> "Unknown shape";
};
Handling null
null
is also supported as a case label with the updates to switch
. This helps with readability and maintainability as all the logic concerning the switch
can be covered in the body of the switch
instead of requiring a null
check before:
interface Shape{
double area();
}
record Circle(double diameter) implements Shape{...};
record Square(double side) implements Shape{...};
Shape shape = new Circle(10);
String message = switch(shape){
case Circle c -> "A circle with area of " + c.area();
case Square s -> "A square with area of " + s.area();
case null -> "Null value";
default -> "Unknown shape";
};
Exhaustiveness
When using pattern matching in switch
, the switch
must be exhaustive, even if it’s written as a statement like in this example:
interface Shape{
double area();
}
record Circle(double diameter) implements Shape{...};
record Square(double side) implements Shape{...};
Shape shape = new Circle(10);
switch(shape){
case Circle c -> System.out.println("A circle with area of " + c.area());
case Square s -> System.out.println("A square with area of " + s.area());
default -> System.out.println("Unknown shape");
};
Sealed Hierachies
When using a sealed hierarchy in a switch
, the compiler can read the hierarchy to test for exhaustiveness. In this example, the default
case is unnecessary as all paths are covered by the switch
. This can help protect against bugs as this will cause a compiler error on this code if a new class is added to the sealed hierarchy in the future.
sealed interface Shape{
double area();
}
record Circle(double diameter) implements Shape{...};
record Square(double side) implements Shape{...};
Shape shape = new Circle(10);
String message = switch(shape){
case Circle c -> "A circle with area of " + c.area();
case Square s -> "A square with area of " + s.area();
//No default needed; compiler can read sealed hierarchy
};
Record patterns
Record patterns also became a final feature in Java 21 and are supported in switch
:
record Name(String fName, String lName, String mName) {};
Name host = new Name("William","Korando",37);
String printName = switch(host) {
case Name(var fName, var lName, var mName) -> lName + ", " + fName + " " + mName;
};
Additional Reading
Pattern Matching for switch - JEP 441
Pattern Matching for instanceof - JEP 394
Happy coding!