Pattern Matching with switch
A switch
expression or statement
transfers control to one of several statements or expressions, depending on the value of its
selector expression. The selector expression can be any reference or primitive
type.
Also, case
labels can have patterns. Consequently, a
switch
expression or statement can test whether its selector
expression matches a pattern, which offers more flexibility and expressiveness compared
to testing whether its selector expression is exactly equal to a constant.
For background information about pattern matching for
switch
expressions and statements, see JEP 441.
Consider the following code that calculates the perimeter of certain shapes from the section Pattern Matching with instanceof:
interface Shape { }
record Rectangle(double length, double width) implements Shape { }
record Circle(double radius) implements Shape { }
// ...
public static double getPerimeter(Shape s) throws IllegalArgumentException {
if (s instanceof Rectangle r) {
return 2 * r.length() + 2 * r.width();
} else if (s instanceof Circle c) {
return 2 * c.radius() * Math.PI;
} else {
throw new IllegalArgumentException("Unrecognized shape");
}
}
You can rewrite this code to use a pattern switch
expression
as follows:
public static double getPerimeter(Shape s) throws IllegalArgumentException {
return switch (s) {
case Rectangle r ->
2 * r.length() + 2 * r.width();
case Circle c ->
2 * c.radius() * Math.PI;
default ->
throw new IllegalArgumentException("Unrecognized shape");
};
}
The following example uses a switch
statement instead of a
switch
expression:
public static double getPerimeter(Shape s) throws IllegalArgumentException {
switch (s) {
case Rectangle r:
return 2 * r.length() + 2 * r.width();
case Circle c:
return 2 * c.radius() * Math.PI;
default:
throw new IllegalArgumentException("Unrecognized shape");
}
}
When Clauses
You can add a Boolean expression right after a pattern label with a
when
clause. This is called a guarded pattern label. The
Boolean expression in the when
clause is called a guard. A value
matches a guarded pattern label if it matches the pattern and, if so, the guard also
evaluates to true. Consider the following example:
static void test(Object obj) {
switch (obj) {
case String s:
if (s.length() == 1) {
System.out.println("Short: " + s);
} else {
System.out.println(s);
}
break;
default:
System.out.println("Not a string");
}
}
You can move the Boolean expression s.length == 1
right
after the case
label with a when
clause:
static void test(Object obj) {
switch (obj) {
case String s when s.length() == 1 -> System.out.println("Short: " + s);
case String s -> System.out.println(s);
default -> System.out.println("Not a string");
}
}
The first pattern label (which is a guarded pattern label) matches if
obj
is both a String
and of length 1. The second
patten label matches if obj
is a String
of a different
length.
A guarded patten label has the form p
when e
where p is a pattern and e
is a
Boolean expression. The scope of any pattern variable declared in p includes e.
The following example also demonstrates when
clauses. It uses a primitive
type, double
, for its switch
expression's selector
expression:
String doubleToRating(double rating) {
return switch(rating) {
case 0d -> "0 stars";
case double d when d > 0d && d < 2.5d
-> d + " is not good";
case double d when d >= 2.5f && d < 5d
-> d + " is better";
case 5d -> "5 stars";
default -> "Invalid rating";
};
}
Note:
Guards containing primitive types is a preview feature. A preview feature is a feature whose design, specification, and implementation are complete, but is not permanent. A preview feature may exist in a different form or not at all in future Java SE releases. To compile and run code that contains preview features, you must specify additional command-line options. See Preview Language and VM Features.See JEP 455: Primitive Types in Patterns, instanceof, and switch (Preview) for additional information.
The following example uses a long
for its selector expression:
void bigNumbers(long v) {
switch (v) {
case long x when x < 1_000_000L ->
System.out.println("Less than a million");
case long x when x < 1_000_000_000L ->
System.out.println("Less than a billion");
case long x when x < 1_000_000_000_000L ->
System.out.println("Less than a trillion");
case long x when x < 1_000_000_000_000_000L ->
System.out.println("Less than a quadrillion");
default -> System.out.println("At least a quadrillion");
}
}
Pattern Label Dominance
It's possible that many pattern labels could match the value of the
selector expression. To help predictability, the labels are tested in the order that they
appear in the switch
block. In addition, the compiler raises an error if a
pattern label can never match because a preceding one will always match first. The following
example results in a compile-time error:
static void error(Object obj) {
switch(obj) {
case CharSequence cs ->
System.out.println("A sequence of length " + cs.length());
// error: this case label is dominated by a preceding case label
case String s ->
System.out.println("A string: " + s);
default -> { break; }
}
}
The first pattern label case CharSequence cs
dominates the second pattern label case String s
because every
value that matches the pattern String s
also matches the pattern
CharSequence cs
but not the other way around. It's because
String
is a subtype of CharSequence
.
A pattern label can dominate a constant label. These examples cause compile-time errors:
static void error2(Integer value) {
switch(value) {
case Integer i ->
System.out.println("Integer: " + i);
// Compile-time errors for both cases -1 and 1:
// this case label is dominated by a preceding case label
case -1, 1 ->
System.out.println("The number 42");
default -> { break; }
}
}
// ...
enum Color { RED, GREEN, BLUE; }
// ...
static void error3(Color value) {
switch(value) {
case Color c ->
System.out.println("Color: " + c);
// error: this case label is dominated by a preceding case label
case RED ->
System.out.println("The color red");
}
}
Note:
Guarded pattern labels don't dominate constant labels. For example:static void testInteger(Integer value) {
switch(value) {
case Integer i when i > 0 ->
System.out.println("Positive integer");
case 1 ->
System.out.println("Value is 1");
case -1 ->
System.out.println("Value is -1");
case Integer i ->
System.out.println("An integer");
}
}
Although the value 1
matches both the guarded pattern
label case Integer i when i > 0
and the constant label
case 1
, the guarded pattern label doesn't dominate the constant
label. Guarded patterns aren't checked for dominance because they're generally
undecidable. Consequently, you should order your case labels so that constant labels
appear first, followed by guarded pattern labels, and then followed by nonguarded
pattern
labels:
static void testIntegerBetter(Integer value) {
switch(value) {
case 1 ->
System.out.println("Value is 1");
case -1 ->
System.out.println("Value is -1");
case Integer i when i > 0 ->
System.out.println("Positive integer");
case Integer i ->
System.out.println("An integer");
}
}
Scope of Pattern Variables and switch
switch
expression or statement, the scope of a
pattern variable declared in a case
label includes the
following:
- The
when
clause of thecase
label:static void test(Object obj) { switch (obj) { case Character c when c.charValue() == 7: System.out.println("Ding!"); break; default: break; } } }
The scope of pattern variable
c
includes thewhen
clause of thecase
label that contains the declaration ofc
. -
The expression, block, or
throw
statement that appears to the right of the arrow of thecase
label:static void test(Object obj) { switch (obj) { case Character c -> { if (c.charValue() == 7) { System.out.println("Ding!"); } System.out.println("Character, value " + c.charValue()); } case Integer i -> System.out.println("Integer: " + i); default -> { break; } } }
The scope of pattern variable
c
includes the block to the right ofcase Character c ->
. The scope of pattern variablei
includes theprintln
statement to the right ofcase Integer i ->
. -
The
switch
-labeled statement group of acase
label:static void test(Object obj) { switch (obj) { case Character c: if (c.charValue() == 7) { System.out.print("Ding "); } if (c.charValue() == 9) { System.out.print("Tab "); } System.out.println("character, value " + c.charValue()); default: // You cannot use the pattern variable c here: break; } }
The scope of pattern variable
c
includes thecase Character c
statement group: the twoif
statements and theprintln
statement that follows them. The scope doesn't include thedefault
statement group even though theswitch
statement can execute thecase Character c
statement group, fall through thedefault
label, and then execute thedefault
statement group.Note:
You will get a compile-time error if it's possible to fall through a case label that declares a pattern variable. The following example doesn't compile:static void test(Object obj) { switch (obj) { case Character c: if (c.charValue() == 7) { System.out.print("Ding "); } if (c.charValue() == 9) { System.out.print("Tab "); } System.out.println("character"); case Integer i: // Compile-time error System.out.println("An integer " + i); default: System.out.println("Neither character nor integer"); } }
If this code were allowed, and the value of the selector expression,
obj
, were aCharacter
, then theswitch
statement can execute thecase Character c
statement group and then fall through thecase Integer i
label, where the pattern variablei
would have not been initialized.See Scope of Pattern Variables and instanceof for more examples of where you can use a pattern variable.
Null case Labels
switch
expressions and switch
statements used to throw a NullPointerException
if the value of the
selector expression is null
. Currently, to add more flexibility, a
null
case label is available:
static void test(Object obj) {
switch (obj) {
case null -> System.out.println("null!");
case String s -> System.out.println("String");
default -> System.out.println("Something else");
}
}
This example prints null!
when obj
is
null
instead of throwing a
NullPointerException
.
You may not combine a null
case label with anything but a
default
case label. The following generates a compiler error:
static void testStringOrNull(Object obj) {
switch (obj) {
// error: invalid case label combination
case null, String s -> System.out.println("String: " + s);
default -> System.out.println("Something else");
}
}
However, the following compiles:
static void testStringOrNull(Object obj) {
switch (obj) {
case String s -> System.out.println("String: " + s);
case null, default -> System.out.println("null or not a string");
}
}
If a selector expression evaluates to null
and the
switch
block does not have null
case label, then a
NullPointerException
is thrown as normal. Consider the following
switch
statement:
String s = null;
switch (s) {
case Object obj -> System.out.println("This doesn't match null");
// No null label; NullPointerException is thrown
// if s is null
}
Although the pattern label case Object obj
matches objects
of type String, this example throws a
NullPointerException
. The selector expression evaluates to
null
, and the switch
expression doesn't contain a
null
case label.