Info
This post is part of a blog post series titled 6 New Features in Java 19 That Make Your Life Easier.
Note
This is a preview feature. In order to use it, compile the program with javac --release 19 --enable-preview Main.java
and run it with java --enable-preview Main
. More details about using preview features in your project can be found here.
Pattern Matching for switch was proposed as a preview feature by JEP 406 and delivered in JDK 17, and proposed for a second preview by JEP 420 and delivered in JDK 18. This JEP proposes a third preview with further refinements based upon continued experience and feedback.
For those who are not familiar with this series of JEPs, I will quickly summarize what the main goals of the “Pattern Matching for switch” JEP series are and provide some examples after. So, the main goals according to the JEP documents are:
- Expand the expressiveness and applicability of switch expressions and statements by allowing patterns to appear in case labels
- Allow the historical null-hostility of switch to be relaxed when desired
- Increase the safety of switch statements by requiring that pattern switch statements cover all possible input values
- Ensure that all existing switch expressions and statements continue to compile with no changes and execute with identical semantics
So what has changed for switch exactly? Let’s have a look.
Broadened Type Support
By extending switch statements and expressions to work on any type and allowing case labels to not only use constant values. In the following pattern switch example the selector expression o
is matched with type patterns involving a class type, an enum type, a record type, and an array type, along with a null case label and a default:
record Point(int i, int j) {} enum Color { RED, GREEN, BLUE; } static void typeTester(Object o) { switch (o) { case null -> System.out.println("null"); case String s -> System.out.println("String"); case Color c -> System.out.println("Color: " + c.toString()); case Point p -> System.out.println("Record class: " + p.toString()); case int[] ia -> System.out.println("Array of ints of length" + ia.length); default -> System.out.println("Something else"); } }
Null Checking
We no longer need to explicitly perform a null check for the switch selector expression outside the switch statement. Checking for null
is now an integral part of switch
and is done by adding a separate case label:
static void testFooBar(String s) { switch (s) { case null -> System.out.println("Oops"); case "Foo", "Bar" -> System.out.println("Great"); default -> System.out.println("Ok"); } }
When Clause for Case Labels
Using the when
clause, we can now specify our switch case label more precisely. There’s no need to add an additional if-check inside the case label:
class Shape {} class Rectangle extends Shape {} class Triangle extends Shape { int calculateArea() { ... } } static void testTriangle(Shape s) { switch (s) { case null -> break; case Triangle t when t.calculateArea() > 100 -> System.out.println("Large triangle"); case Triangle t -> System.out.println("Small triangle"); default -> System.out.println("Non-triangle"); } }
Exhaustiveness of Switch Expressions and Statements
It is required that all possible values of the selector expression are handled in the switch block. The example below is not exhaustive and will cause a compilation error:
static int coverage(Object o) { return switch (o) { // Error - not exhaustive case String s -> s.length(); case Integer i -> i; }; }
But this example is exhausting (and thus valid):
static int coverage(Object o) { return switch (o) { case String s -> s.length(); case Integer i -> i; default -> 0; }; }
With the most recent additions, switch
has become much more powerful and helps us to keep our code nice and clean. The examples shown above should just give an overview of how the code could look. The JEPs go much more into detail in the areas described above.