JDK-8222184 : JEP 354: Switch Expressions (Second Preview)
  • Type: JEP
  • Component: specification
  • Sub-Component: language
  • Priority: P2
  • Status: Closed
  • Resolution: Delivered
  • Fix Versions: 13
  • Submitted: 2019-04-09
  • Updated: 2021-08-28
  • Resolved: 2019-08-16
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Sub Tasks
JDK-8226915 :  
Description
Summary
-------

Extend `switch` so it can be used as either a statement or an expression, and so that both forms can use either traditional `case ... :` labels (with fall through) or new `case ... ->` labels (with no fall through), with a further new statement for yielding a value from a `switch` expression. These changes will simplify everyday coding, and prepare the way for the use of [pattern matching (JEP 305)](https://openjdk.java.net/jeps/305) in `switch`.  This is a [preview language feature](http://openjdk.java.net/jeps/12) in JDK 13.


History
-------

Switch expressions were [proposed in December 2017](https://mail.openjdk.java.net/pipermail/amber-dev/2017-December/002412.html) by [JEP 325](https://openjdk.java.net/jeps/325). They were [targeted to JDK 12 in August 2018](https://mail.openjdk.java.net/pipermail/jdk-dev/2018-August/001827.html) as a [preview feature](https://openjdk.java.net/jeps/12). Feedback was sought initially on the design of the feature, and later on the experience of using `switch` expressions and the enhanced `switch` statement. Based on that feedback, this JEP makes one change to the feature:

> To yield a value from a `switch` expression, the `break` with value statement is dropped in favor of a `yield` statement.


Motivation
----------

As we prepare to enhance the Java programming language to support [pattern matching (JEP 305)](https://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 between switch labels (fall through), the default scoping in switch blocks (the whole block is treated as one scope), and the fact that `switch` works only as a statement, even though it is often 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 would mean accidental fall through.

    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, "`case L ->`", to signify that only the code to the right of the label is to be executed if the label is matched. We also propose to allow multiple constants per case, separated by commas. 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);
    }

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 is 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 block:

    switch (day) {
        case MONDAY:
        case TUESDAY:
            int temp = ...     // The scope of 'temp' continues to the }
            break;
        case WEDNESDAY:
        case THURSDAY:
            int temp2 = ...    // Can't call this variable 'temp'
            break;
        default:
            int temp3 = ...    // Can't call this variable '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
-----------

### Arrow labels

In addition to traditional "`case L :`" labels in a switch block, we propose a new simplified form, with "`case L ->`" labels. If a label is matched, then only the expression or statement to the right of the arrow is executed; there is no fall through. For example, given the following `switch` statement that uses the new form of labels:

    static void howMany(int k) {
        switch (k) {
            case 1  -> System.out.println("one");
            case 2  -> System.out.println("two");
            default -> System.out.println("many");
        }
    }

The following code:

    howMany(1);
    howMany(2);
    howMany(3);

results in the following output:

    one
    two
    many

### Switch expressions

We will extend the `switch` statement so it can be used as an expression. For example, the previous `howMany` method can be rewritten to use a `switch` expression, so it uses only a single `println`. 

    static void howMany(int k) {
        System.out.println(
            switch (k) {
                case  1 -> "one"
                case  2 -> "two"
                default -> "many"
            }
        );
    }

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.

### Yielding a value

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 introduce a new `yield` statement to yield a value, 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);
            yield 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 are yielded using the new `yield` statement:

    int result = switch (s) {
        case "Foo": 
            yield 1;
        case "Bar":
            yield 2;
        default:
            System.out.println("Neither Foo nor Bar, hmmm...");
            yield 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 two statements, `break` (with or without a label) and `yield`, facilitate easy disambiguation between `switch` statements and `switch` expressions: a `switch` statement but not a `switch` expression can be the target of a `break` statement; and a `switch` expression but not a `switch` statement can be the target of a `yield` statement.

> In the previous preview version of `switch` expressions, [JEP 325](https://openjdk.java.net/jeps/325), we proposed to add a new form of `break` statement with a value, which would be used to yield a value from a `switch` expression. In this version of `switch` expressions, this will be replaced with the new `yield` statement.  

### Exhaustiveness

The cases of a `switch` expression must be _exhaustive_; for all possible values there must be a matching switch label. (Obviously `switch` statements are not required to be exhaustive.)

In practice this normally means that a `default` clause is required; however, in the case of an `enum` `switch` expression that covers all known constants, a `default` clause is inserted by the compiler to indicate that the `enum` definition has changed between compile-time and runtime. Relying on this implicit `default` clause insertion makes for more robust code; now when code is recompiled, the compiler checks that all cases are explicitly handled. Had the developer inserted an explicit `default` clause (as is the case today) a possible error will have been hidden.

Furthermore, a `switch` expression must either complete normally with a value, or complete abruptly by throwing 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 yield statement
        }
        default -> 1;
    };
    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
    };

A further consequence is that the control statements, `break`, `yield`, `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:  
                    yield 1;
                case 1:
                    yield 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
------------

This feature was previewed in [JEP 325](https://openjdk.java.net/jeps/325).

[Pattern Matching (JEP 305)](https://openjdk.java.net/jeps/305) depends on this JEP.


Risks and Assumptions
---------------------

The need for a `switch` statement with `case L -> ` labels is sometimes unclear. The following rationale presents the assumptions behind its inclusion:

- There are `switch` statements that operate by side-effects, but which are generally still “one action per label”. Bringing these into the fold with new-style labels makes the statements more straightforward and less error-prone.

- The fact that the default control flow in a `switch` statement's block is to fall through, rather than to break out, was an unfortunate choice early in Java's history. It is a matter of huge angst for developers. This seemed to be something that should be solved for the `switch` construct in general, not just for `switch` expressions.

- It seemed more desirable to tease the desired benefits (expression-ness, better control flow, saner scoping) into orthogonal features, so that `switch` expressions and `switch` statements could have more in common. The greater the divergence between `switch` expressions and `switch` statements, the more complex the language is to learn, and the more sharp edges there are for developers to cut themselves on.

Comments
Is this Proposed to Target JDK 13? If so, please set the Fix Version field to 13.
29-05-2019