JDK-8192963 : JEP 325: Switch Expressions (Preview)
  • Type: JEP
  • Component: tools
  • Priority: P3
  • Status: Closed
  • Resolution: Delivered
  • Fix Versions: 12
  • Submitted: 2017-12-04
  • Updated: 2019-06-14
  • Resolved: 2019-01-29
Related Reports
Blocks :  
Blocks :  
Relates :  
Relates :  
Relates :  
Sub Tasks
JDK-8218616 :  
Description
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.