11 Switch Expressions and Statements
You can use the switch keyword as either a statement or an
expression. Like all expressions, switch expressions evaluate to a single
value and can be used in statements. Switch expressions may contain "case L
->" labels that eliminate the need for break statements to
prevent fall through. You can use a yield statement to specify the value of
a switch expression.
For background information about the design of switch expressions, see
JEP 361.
Arrow Cases
Consider the following switch statement that prints the
number of letters of a day of the week:
public enum Day { SUNDAY, MONDAY, TUESDAY,
WEDNESDAY, THURSDAY, FRIDAY, SATURDAY; }
// ...
int numLetters = 0;
Day day = Day.WEDNESDAY;
switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
numLetters = 6;
break;
case TUESDAY:
numLetters = 7;
break;
case THURSDAY:
case SATURDAY:
numLetters = 8;
break;
case WEDNESDAY:
numLetters = 9;
break;
}
System.out.println(numLetters);It would be better if you could "return" the length of the day's name instead
of storing it in the variable numLetters; you can do this with a
switch expression. Furthermore, it would be better if you didn't
need break statements to prevent fall through; they are laborious to
write and easy to forget. You can do this with an arrow case. The following is a
switch expression that uses arrow cases to print the number of
letters of a day of the week:
Day day = Day.WEDNESDAY;
System.out.println(
switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
}
); An arrow case has the following form:
case label_1, label_2, ..., label_n -> expression;|throw-statement;|block When the Java runtime matches any of the labels to the left of the arrow, it
runs the code to the right of the arrow and does not fall through; it does not run any
other code in the switch expression (or statement). If the code to the
right of the arrow is an expression, then the value of that expression is the value of
the switch expression.
You can use arrow cases in switch statements. The following
is like the first example, except it uses arrow cases instead of colon cases:
int numLetters = 0;
Day day = Day.WEDNESDAY;
switch (day) {
case MONDAY, FRIDAY, SUNDAY -> numLetters = 6;
case TUESDAY -> numLetters = 7;
case THURSDAY, SATURDAY -> numLetters = 8;
case WEDNESDAY -> numLetters = 9;
};
System.out.println(numLetters);An arrow case along with its code to its right is called a
switch-labeled rule.
Colon Cases and the the yield Statement
A colon case is a case label in the form
case L:. You can use colon cases in switch
expressions. A colon case along with its code to the right is called a
switch-labeled statement group:
Day day = Day.WEDNESDAY;
int numLetters = switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
System.out.println(6);
yield 6;
case TUESDAY:
System.out.println(7);
yield 7;
case THURSDAY:
case SATURDAY:
System.out.println(8);
yield 8;
case WEDNESDAY:
System.out.println(9);
yield 9;
};
System.out.println(numLetters);The previous example uses yield statements. They take one
argument, which is the value that the colon case produces in a switch
expression.
The yield statement makes it easier for you to differentiate
between switch statements and switch expressions. A
switch statement, but not a switch expression, can
be the target of a break statement. Conversely, a
switch expression, but not a switch statement, can
be the target of a yield statement.
Note:
It's recommended that you use arrow cases. It's easy to forget to insert
break or yield statements when using colon
cases; if you do, you might introduce unintentional fall through in your code.
For arrow cases, to specify multiple statements or code that are not
expressions or throw statements, enclose them in a block. Specify
the value that the arrow case produces with the yield
statement:
int numLetters = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> {
System.out.println(6);
yield 6;
}
case TUESDAY -> {
System.out.println(7);
yield 7;
}
case THURSDAY, SATURDAY -> {
System.out.println(8);
yield 8;
}
case WEDNESDAY -> {
System.out.println(9);
yield 9;
}
};
Qualified enum Constants as case Constants
You can use qualified enum constants as
case constants in switch expressions and
statements.
Consider the following switch expression whose selector expression is an
enum type:
public enum Standard { SPADE, HEART, DIAMOND, CLUB }
// ...
static void determineSuitStandardDeck(Standard d) {
switch (d) {
case SPADE -> System.out.println("Spades");
case HEART -> System.out.println("Hearts");
case DIAMOND -> System.out.println("Diamonds");
default -> System.out.println("Clubs");
}
}In the following example, the type of the selector expression is an
interface that's been implemented by two enum types. Because the type
of the selector expression isn't an enum type, this
switch expression uses guarded patterns instead:
sealed interface CardClassification permits Standard, Tarot {}
public enum Standard implements CardClassification
{ SPADE, HEART, DIAMOND, CLUB }
public enum Tarot implements CardClassification
{ SPADE, HEART, DIAMOND, CLUB, TRUMP, EXCUSE }
// ...
static void determineSuit(CardClassification c) {
switch (c) {
case Standard s when s == Standard.SPADE -> System.out.println("Spades");
case Standard s when s == Standard.HEART -> System.out.println("Hearts");
case Standard s when s == Standard.DIAMOND -> System.out.println("Diamonds");
case Standard s -> System.out.println("Clubs");
case Tarot t when t == Tarot.SPADE -> System.out.println("Spades or Piques");
case Tarot t when t == Tarot.HEART -> System.out.println("Hearts or C\u0153ur");
case Tarot t when t == Tarot.DIAMOND -> System.out.println("Diamonds or Carreaux");
case Tarot t when t == Tarot.CLUB -> System.out.println("Clubs or Trefles");
case Tarot t when t == Tarot.TRUMP -> System.out.println("Trumps or Atouts");
case Tarot t -> System.out.println("The Fool or L'Excuse");
}
}However, switch expressions and statements allow qualified
enum constants, so you could rewrite this example as follows:
static void determineSuitQualifiedNames(CardClassification c) {
switch (c) {
case Standard.SPADE -> System.out.println("Spades");
case Standard.HEART -> System.out.println("Hearts");
case Standard.DIAMOND -> System.out.println("Diamonds");
case Standard.CLUB -> System.out.println("Clubs");
case Tarot.SPADE -> System.out.println("Spades or Piques");
case Tarot.HEART -> System.out.println("Hearts or C\u0153ur");
case Tarot.DIAMOND -> System.out.println("Diamonds or Carreaux");
case Tarot.CLUB -> System.out.println("Clubs or Trefles");
case Tarot.TRUMP -> System.out.println("Trumps or Atouts");
case Tarot.EXCUSE -> System.out.println("The Fool or L'Excuse");
}
}Therefore, you can use an enum constant when the type of the selector
expression is not an enum type provided that the enum
constant's name is qualified and its value is assignment-compatible with the type of the
selector expression.
Primitive Values in switch Expressions and Statements
In addition to the primitive types char,
byte, short, and int, a
switch expression or statement's selector expression can be of type
long, float, double, and
boolean, as well as the corresponding boxed types.
Note:
This 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 507: Primitive Types in Patterns, instanceof, and switch (Third Preview) for additional information.
If a selector expression is of type long,
float, double, and boolean, then
its case labels must have the same type as the selector expression or its corresponding
boxed type. For example:
void whichFloat(float v) {
switch (v) {
case 0f ->
System.out.println("Zero");
case float x when x > 0f && x <= 10f ->
System.out.println(x + " is between 1 and 10");
case float x ->
System.out.println(x + " is larger than 10");
}
}If you change the case label 0f to 0, you would get the
following compile-time error:
error: constant label of type int is not compatible with switch selector type floatYou can't use two floating-point literals as case labels
that are representationally equivalent. (See the JavaDoc API documentation for
the Double class for more information about representation
equivalence.) The following example generates a compiler error. The value
0.999999999f is representationally equivalent to
1.0f:
void duplicateLabels(float v) {
switch (v) {
case 1.0f -> System.out.println("One");
// error: duplicate case label
case 0.999999999f -> System.out.println("Almost one");
default -> System.out.println("Another float value");
}
}Switching on boolean values is a useful alternative to the ternary
conditional operator (?:) because a boolean
switch expression or statement can contain statements as well as
expressions. For example, the following code uses a boolean
switch expression to perform some logging when
false:
long startProcessing(OrderStatus.NEW, switch (user.isLoggedIn()) {
case true -> user.id();
case false -> { log("Unrecognized user"); yield -1L; }
});Exhaustiveness of switch
The cases of a switch expression or statement must be
exhaustive, which means that for all possible values, there must be a matching
case label. Thus, a switch expression or statement
normally require a default clause. However, for an enum
switch expression that covers all known constants, the compiler inserts an
implicit default clause, like the examples at the beginning of this section
that print the number of letters in name of a day of the week.
The cases of a switch statement must be exhaustive if it uses pattern or
null labels. See Pattern Matching with switch and Null case Labels for more information.
The following switch expression is not exhaustive and
generates a compile-time error. The type coverage of its labels consist of the subtypes
of String and Integer. However, it doesn't include the
type of the selector expression, Object:
static int coverage(Object obj) {
return switch (obj) { // Error - not exhaustive
case String s -> s.length();
case Integer i -> i;
};
}However, the type coverage of the case label default is all
types, so the following example compiles:
static int coverage(Object obj) {
return switch (obj) {
case String s -> s.length();
case Integer i -> i;
default -> 0;
};
}Consequently, for a switch expression or statement to be
exhaustive, the type coverage of its labels must include the type of the selector
expression.
The compiler takes into account whether the type of a selector expression is
a sealed class. The following switch expression compiles. It doesn't
need a default case label because its type coverage is the classes
A, B, and C, which are the only
permitted subclasses of S, the type of the selector expression:
sealed interface S permits A, B, C { }
final class A implements S { }
final class B implements S { }
record C(int i) implements S { } // Implicitly final
//...
static int testSealedCoverage(S s) {
return switch (s) {
case A a -> 1;
case B b -> 2;
case C c -> 3;
};
}The compiler can also determine the type coverage of a
switch expression or statement if the type of its selector
expression is a generic sealed class. The following example compiles. The only permitted
subclasses of interface I are classes A and
B. However, because the selector expression is of type
I<Integer>, the switch block requires only
class B in its type coverage to be exhaustive:
sealed interface I<T> permits A, B {}
final class A<X> implements I<String> {}
final class B<Y> implements I<Y> {}
static int testGenericSealedExhaustive(I<Integer> i) {
return switch (i) {
// Exhaustive as no A case possible!
case B<Integer> bi -> 42;
};
}The type of a switch expression or statement's selector
expression can also be a generic record. As always, a switch expression
or statement must be exhaustive. The following example doesn't compile. No match for a
Pair exists that contains two values, both of type
A:
record Pair<T>(T x, T y) {}
class A {}
class B extends A {}
static void notExhaustive(Pair<A> p) {
switch (p) {
// error: the switch statement does not cover all possible input values
case Pair<A>(A a, B b) -> System.out.println("Pair<A>(A a, B b)");
case Pair<A>(B b, A a) -> System.out.println("Pair<A>(B b, A a)");
}
}The following example compiles. Interface I is sealed. Types C and D cover all possible instances:
record Pair<T>(T x, T y) {}
sealed interface I permits C, D {}
record C(String s) implements I {}
record D(String s) implements I {}
// ...
static void exhaustiveSwitch(Pair<I> p) {
switch (p) {
case Pair<I>(I i, C c) -> System.out.println("C = " + c.s());
case Pair<I>(I i, D d) -> System.out.println("D = " + d.s());
}
}If a switch expression or statement is exhaustive at
compile time but not at run time, then a MatchException is
thrown. This can happen when a class that contains an exhaustive switch
expression or statement has been compiled, but a sealed hierarchy that is used in the
analysis of the switch expression or statement has been subsequently
changed and recompiled. Such changes are migration incompatible and may lead to a
MatchException being thrown when running the
switch statement or expression. Consequently, you need to recompile
the class containing the switch expression or statement.
Consider the following class PrintA and sealed interface
OnlyAB:
class PrintA {
public static void main(String[] args) {
System.out.println(switch (OnlyAB.getAValue()) {
case A a -> 1;
case B b -> 2;
});
}
}sealed interface OnlyAB permits A, B {
static OnlyAB getAValue() {
return new A();
}
}
final class A implements OnlyAB {}
final class B implements OnlyAB {}The switch expression in the class PrintA
is exhaustive and this example compiles. When you run PrintA, it prints
the value 1. However, suppose you edit OnlyAB as
follows and compile this interface and not
PrintA:
sealed interface OnlyAB permits A, B, C {
static OnlyAB getAValue() {
return new A();
}
}
final class A implements OnlyAB {}
final class B implements OnlyAB {}
final class C implements OnlyAB {}When you run PrintA, it throws a MatchException:
Exception in thread "main" java.lang.MatchException
at PrintA.main(PrintA.java:3)Completion and switch Expressions
A switch expression must either complete normally with a
value or complete abruptly by throwing an exception
A. For example, the following code doesn't compile because the
switch labeled rule doesn't contain a yield
statement:
int i = switch (day) {
case MONDAY -> {
System.out.println("Monday");
// error: block doesn't contain a yield statement
}
default -> 1;
};The following example doesn't compile because the switch
labeled statement group doesn't contain a yield statement:
i = switch (day) {
case MONDAY, TUESDAY, WEDNESDAY:
yield 0;
default:
System.out.println("Second half of the week");
// error: group doesn't contain a yield statement
};Because a switch expression must evaluate to a single value
(or throw an exception), you can't jump through a switch expression
with a break, yield, return, or
continue statement, like in the following example:
z:
for (int i = 0; i < MAX_VALUE; ++i) {
int k = switch (e) {
case 0:
yield 1;
case 1:
yield 2;
default:
continue z;
// error: illegal jump through a switch expression
};
// ...
}