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) in switch. This will be a preview language feature.

Motivation

As we prepare to enhance the Java programming language to support pattern matching (JEP 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.

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:

(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.

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 switchexpression, which is both clearer and safer:

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:

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.

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:

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 enumswitch 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.