|
Blocks :
|
|
|
Blocks :
|
|
|
Relates :
|
|
|
Relates :
|
|
|
Relates :
|
|
|
Relates :
|
|
JDK-8218616 :
|
Summary
------------
Extend the `switch` statement so that it can be used as either a statement or an expression, and that both forms can use either a "traditional" or "simplified" scoping and control flow behavior. These changes will simplify everyday coding, and also prepare the way for the use of [pattern matching (JEP 305)](http://openjdk.java.net/jeps/305) in `switch`. This is a [preview language feature](http://openjdk.java.net/jeps/12) in JDK 12.
_Please note: this JEP is superseded by [JEP 354](https://openjdk.java.net/jeps/354), which targets JDK 13._
Motivation
--------------
As we prepare to enhance the Java programming language to support [pattern matching (JEP 305)](http://openjdk.java.net/jeps/305), several irregularities of the existing `switch` statement -- which have long been an irritation to users -- become impediments. These include the default control flow behavior (fall through) of switch blocks, the default scoping of switch blocks (the block is treated as one single scope) and that `switch` works only as a statement, even though it is commonly more natural to express multi-way conditionals as expressions.
The current design of Java's `switch` statement follows closely languages such as C and C++, and supports fall-through semantics by default. Whilst this traditional control flow is often useful for writing low-level code (such as parsers for binary encodings), as switch is used in higher-level contexts, its error-prone nature starts to outweigh its flexibility.
For example, in the following code, the many `break` statements make it unnecessarily verbose, and this visual noise often masks hard to debug errors, where missing `break` statements mean that accidental fall-through occurs.
switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
System.out.println(6);
break;
case TUESDAY:
System.out.println(7);
break;
case THURSDAY:
case SATURDAY:
System.out.println(8);
break;
case WEDNESDAY:
System.out.println(9);
break;
}
We propose to introduce a new form of switch label, written "`case L ->`" to signify that only the code to the right of the label is to be executed if the label is matched. For example, the previous code can now be written:
switch (day) {
case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
case TUESDAY -> System.out.println(7);
case THURSDAY, SATURDAY -> System.out.println(8);
case WEDNESDAY -> System.out.println(9);
}
(This example also uses multiple case labels: we propose to support multiple comma-separated labels in a single switch label.)
The code to the right of a "`case L ->`" switch label is restricted to be an expression, a block, or (for convenience) a `throw` statement. This has the pleasing consequence that should an arm introduce a local variable, it must be contained in a block and thus not in scope for any of the other arms in the switch block. This eliminates another annoyance with "traditional" switch blocks where the scope of a local variable is the entire switch block.
switch (day) {
case MONDAY:
case TUESDAY:
int temp = ...
break;
case WEDNESDAY:
case THURSDAY:
int temp2 = ... // Why can't I call this temp?
break;
default:
int temp3 = ... // Why can't I call this temp?
}
Many existing `switch` statements are essentially simulations of `switch` expressions, where each arm either assigns to a common target variable or returns a value:
int numLetters;
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;
default:
throw new IllegalStateException("Wat: " + day);
}
Expressing this as a statement is roundabout, repetitive, and error-prone. The author meant to express that we should compute a value of `numLetters` for each day. It should be possible to say that directly, using a `switch` _expression_, which is both clearer and safer:
int numLetters = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
};
In turn, extending `switch` to support expressions raises some additional needs, such as extending flow analysis (an expression must always compute a value or complete abruptly), and allowing some case arms of a `switch` expression to throw an exception rather than yield a value.
Description
-----------
In additional to "traditional" switch blocks, we propose to add a new "simplified" form, with new "`case L ->`" switch labels. If a label is matched, then only the expression or statement to the right of an arrow label is executed; there is no fall through. For example, given the method:
static void howMany(int k) {
switch (k) {
case 1 -> System.out.println("one");
case 2 -> System.out.println("two");
case 3 -> System.out.println("many");
}
}
The following code:
howMany(1);
howMany(2);
howMany(3);
results in the following output:
one
two
many
We will extend the `switch` statement so that it can additionally be used as an expression. In the common case, a `switch` expression will look like:
T result = switch (arg) {
case L1 -> e1;
case L2 -> e2;
default -> e3;
};
A `switch` expression is a poly expression; if the target type is known, this type is pushed down into each arm. The type of a `switch` expression is its target type, if known; if not, a standalone type is computed by combining the types of each case arm.
Most `switch` expressions will have a single expression to the right of the "`case L ->`" switch label. In the event that a full block is needed, we have extended the `break` statement to take an argument, which becomes the value of the enclosing `switch` expression.
int j = switch (day) {
case MONDAY -> 0;
case TUESDAY -> 1;
default -> {
int k = day.toString().length();
int result = f(k);
break result;
}
};
A `switch` expression can, like a `switch` statement, also use a "traditional" switch block with "`case L:`" switch labels (implying fall-through semantics). In this case values would be yielded using the `break` with value statement:
int result = switch (s) {
case "Foo":
break 1;
case "Bar":
break 2;
default:
System.out.println("Neither Foo nor Bar, hmmm...");
break 0;
};
The two forms of `break` (with and without value) are analogous to the two forms of `return` in methods. Both forms of `return` terminate the execution of the method immediately; in a non-void method, additionally a value must be provided which is yielded to the invoker of the method. (Ambiguities between the `break` expression-value and `break` label forms can be handled relatively easily.)
The cases of a `switch` expression must be exhaustive; for any possible value there must be a matching switch label. In practice this normally means simply that a `default` clause is required; however, in the case of an `enum` `switch` expression that covers all known cases (and eventually, `switch` expressions over sealed types), a `default` clause can be inserted by the compiler that indicates that the `enum` definition has changed between compile-time and runtime. (This is what developers do by hand today, but having the compiler insert it is both less intrusive and likely to have a more descriptive error message than the ones written by hand.)
Furthermore, a `switch` expression must complete normally with a value, or `throw` an exception. This has a number of consequences. First, the compiler checks that for every switch label, if it is matched then a value can be yielded.
int i = switch (day) {
case MONDAY -> {
System.out.println("Monday");
// ERROR! Block doesn't contain a break with value
}
default -> 1;
};
i = switch (day) {
case MONDAY, TUESDAY, WEDNESDAY:
break 0;
default:
System.out.println("Second half of the week");
// ERROR! Group doesn't contain a break with value
};
A further consequence is that the control statements, `break`, `return` and `continue`, cannot jump through a `switch` expression, such as in the following:
z:
for (int i = 0; i < MAX_VALUE; ++i) {
int k = switch (e) {
case 0:
break 1;
case 1:
break 2;
default:
continue z;
// ERROR! Illegal jump through a switch expression
};
...
}
As a target of opportunity, we may expand switch to support switching on primitive types (and their box types) that have previously been disallowed, such as float, double, and long.
Dependencies
----------------
[Pattern Matching (JEP 305)](http://openjdk.java.net/jeps/305) depends on this JEP.