10 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 455: Primitive Types in Patterns, instanceof, and switch (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 float
You 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
};
// ...
}