A new switch for a new coding world

OpenJDK JEP 354 states that the new switch statement will:

“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 in switch.”

The motivation for the changes, according to the JEP, was to support pattern matching (described in JEP 305) and enable switch to be used in higher-level code-crafting contexts by eliminating several irregularities in the existing switch statement, such as:

  • The default control flow behavior of switch blocks (which supports fall-through semantics)
  • The default scoping of switch blocks (in which the block is treated as one single scope)
  • The fact that switch works only as a statement (even though it is more natural to express multi-way conditionals as expressions)

I first introduced the new switch statement and expression feature in the tutorial “Java theory and practice: Explore new Java SE 11 and 12 APIs and language features” (IBM Developer, April 2019). (See the section “The enhanced switch statement and new switch expression syntax.”)

Now I will bring you back to this new Java language feature that is designed to extend and enhance the use of the current switch statement to be clearer, more concise, less boilerplate, and, more importantly, to avoid the fall-through problem. I will also introduce you to the new switch expression.

Working with the required software

There are no exercise files for this tutorial, but I encourage you to follow along with me and get some hands-on experience with the feature, so you may now start your new JShell session in your preferred way to begin hacking.

All you need for working with JShell in JDK 12 is the release itself; JShell is integrated into the release.

JShell is the interactive Java shell REPL tool that was added to Java in version 9. You can find more in-depth knowledge on working with JShell in:

Project Amber

While Java evolves, introducing ever more new features in an accelerated release schedule, the process spawns many separate side projects that focus on specific aspects of the language instead of attempting to manage all the projects under the single umbrella of JDK development.

A benefit of this approach allows any group of internal JDK engineers and interested community members to participate in the progression of these projects by giving them the opportunity to focus on, experiment with, and test the proposed features. Once the features are visible, completed, and approved then they are merged with the JDK releases.

This makes it easier to add many more features to each new JDK release every 6 months, which in turn pushes the Java programming language to be more innovative more quickly, meaning that the language can keep up with the technical demands of market-driven application development.

The topic of this tutorial is the central feature of one of those projects — Project Amber. The goal of this project was to explore and incubate smaller, productivity-oriented Java language features, including JEP 354 (formerly JEP 325) on switch expressions.

As Java builders move to support pattern matching, irregularities in the existing switch statement become impediments. These include the default control flow behavior of switch blocks; default scoping of switch blocks in which the block is treated as one single scope; and switch working only as a statement.

The current design of Java’s switch statement follows languages such as C++ and, by default, supports fall-through semantics. This control flow has been useful for writing low-level code, but as switch is used in higher-level contexts, its error-prone nature begins to outweigh flexibility.

What is pattern matching for Java?

Pattern matching is a technique that has been adapted to many different styles of programming languages going back to the 1960s, including text-oriented languages like SNOBOL4 and AWK, functional languages like Haskell and ML, and more recently extended to object-oriented languages like Scala (and most recently, C#).

Pattern matching allows common logic in a program to conditionally extract components from objects and to be expressed more concisely and safely, which will enhance the language in many constructs; for example, it enhances the instanceof operator and the switch expressions. (The first construct has not been proposed for a specific JDK version; switch expressions are designated for JDK 12.)

But that’s enough theory; let’s start hacking the new switch statement enhancements, which will be added to the existing switch statement.

Enhancing the switch

Before I jump into the examples, let’s talk syntax:

  • There is a new arrow syntax (->) that can be used with a switch statement or with a switch expression; this is known as the switch labeled rule
  • The traditional colon syntax (:) can also be used with either a switch expression or a traditional switch statement; this is known as the switch labeled statement group

In other words, the colon and the arrow syntax can be used with both cases, so the presence of the colon does not necessarily imply a switch statement and the presence of an arrow does not necessarily imply a switch expression.

Now that I’ve cleared that up, let’s look at traditional switch statements.

The traditional switch statement

From a JShell session, I will normally use the traditional switch statement, a staple of Java language from its beginning. Now let’s say that you want to create a function with the name getRomanNumber(int value) to return a String Roman numeral value equivalent to the passed integer value using a normal switch statement:

mohamed_taman:~$ jshell --enable-preview
|  Welcome to JShell -- Version 12-ea
|  For an introduction type: /help intro

jshell> String getRomanNumber(int value){
    String romanValue = "";
    switch(value){
       case 0: romanValue = "nulla";
             break;
       case 1: romanValue = "I";
             break;
       case 2: romanValue = "II";
             break;
       case 3: romanValue = "III";
             break;
       case 4: romanValue = "IV";
             break;
       case 5: romanValue = "V";
             break;
       case 6: romanValue = "VI";
             break;
       case 7: romanValue = "VII";
             break;
       case 8: romanValue = "VIII";
             break;
       case 9: romanValue = "IX";
             break;
       case 10: romanValue = "X";
             break;
       default: System.out.printf("Out of range value: %d %n", value);
romanValue = "N/A";
                break;
    }
   return romanValue;
}
|  created method getRomanNumber(int)

jshell>getRomanNumber(3)
$2 ==> "III"

jshell>getRomanNumber(11)
Out of range value: 11
$6 ==> "N/A"

Although this function works perfectly, it is lengthy. The many break statements introduce a visual static which often makes it difficult to spot debug errors. It also, in my opinion, makes it unnecessarily verbose. And if you miss placing a break statement, then accidental fall-through errors will occur.

The enhanced switch statement

The proposed new form of switch label, written as case L ->, signifies 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 as:

jshell>/edit getRomanNumber

JShell will open the previous method in a JShell Edit Pad to make modification easier. Modify the method switch statement with the following new switch label form:

String getRomanNumber(int value){
    String romanValue = "";
    switch(value){
       case 0 ->romanValue = "nulla";
       case 1 ->romanValue = "I";
       case 2 ->romanValue = "II";
       case 3 ->romanValue = "III";
       case 4 ->romanValue = "IV";
       case 5 ->romanValue = "V";
       case 6 ->romanValue = "VI";
       case 7 ->romanValue = "VII";
       case 8 ->romanValue = "VIII";
       case 9 ->romanValue = "IX";
       case 10 ->romanValue = "X";
       default -> {
              System.out.printf("Out of range value: %d %n", value);
romanValue = "N/A";
              }
    }

    return romanValue;
}

When you’ve finished your modifications, click Exit to accept the modifications so you can return back to the current jshell> session prompt and try method with some values, as shown here:

jshell> /edit getRomanNumber1
|  modified method getRomanNumber(int)

jshell>getRomanNumber(5)
$10 ==> "V"

jshell>getRomanNumber(20)
Out of range value: 20
$11 ==> "N/A"

It works perfectly. The previews code shows the switch still being used as a statement, while it takes advantage of the new arrow syntax form to accomplish its switching without the explicit specification of a break.

This code is less verbose, but perhaps more importantly, it eliminates the often-dreaded switch fall-through case.

Notice that the code to the right of a case L -> label is restricted to be an expression, a block, or a throw statement (for convenience).

Multiple comma-separated labels in a single switch case

Traditionally in a switch statement, when you need to identify that multiple cases should execute the same set of statements, you code it as follows:

jshell>String getOddOrEvenNumber(int value){
    String kind = "N/A";
    switch(value){
       case 0: kind = "Zero"; break;
       case 1:
       case 3:
       case 5:
       case 7:
       case 9: kind = "Odd"; break;
       case 2:
       case 4:
       case 6:
       case 8:
       case 10: kind = "Even"; break;
       default: System.out.printf("Out of range value: %d %n", value);
    }
   return kind;
}
|  created method getOddOrEvenNumber(int)

jshell>getOddOrEvenNumber(3)
$8 ==> "Odd"

jshell>getOddOrEvenNumber(10)
$9 ==> "Even"

jshell>getOddOrEvenNumber(12)
Out of range value: 12
$10 ==> "N/A"

Now let’s rewrite it in a more concise, clear way, thanks to the addition of an enhancement that allows us to handle multiple comma-separated labels in a single switch case. Use the edit command again to edit the method:

jshell> /edit getOddOrEvenNumber

In the JShell Edit Pad, modify the method like this:

String getOddOrEvenNumber(int value){
    String kind = "N/A";
    switch(value){
       case 0: kind = "Zero"; break;
       case 1, 3, 5, 7, 9: kind = "Odd"; break;
       case 2, 4, 6, 8, 10: kind = "Even"; break;
       default: throw new IllegalStateException("Out of range value:" + value);
    }
   return kind;
}

When you’re finished, click Exit then add some values to it to see it in action:

jshell> /edit getOddOrEvenNumber
|  modified method getOddOrEvenNumber(int)

jshell>getOddOrEvenNumber(10)
$16 ==> "Even"

jshell>getOddOrEvenNumber(12)
|  Exceptionjava.lang.IllegalStateException: Out of range value: 12
|        at getOddOrEvenNumber (#15:7)
|        at (#17:1)

jshell>

What do you think? Much clearer isn’t it? You could even make it more break-less:

String getOddOrEvenNumber(int value){
    String kind = "N/A";
    switch(value){
       case 0 -> kind = "Zero";
       case 1, 3, 5, 7, 9 -> kind = "Odd”;
       case 2, 4, 6, 8, 10 -> kind = "Even";
       default ->throw new IllegalStateException("Out of range value:" + value);
    }
   return kind;
}

Try this method with different values and see how it works.

The new switch expression

Actually, many existing switch statements you use in daily code (including the previous example) are essentially simulations of switch expressions in which each arm either assigns to a common target variable or returns a value.

Now we can make use of the new switch expression to return the value directly to be assigned to the target variable. Modify the getOddOrEvenNumber(int value) method:

String getOddOrEvenNumber(int value){
    String kind = "N/A";
    switch(value){
       case 0: kind = "Zero"; break;
       case 1:
       case 3:
       case 5:
       case 7:
       case 9: kind = "Odd"; break;
       case 2:
       case 4:
       case 6:
       case 8:
       case 10: kind = "Even"; break;
       default: System.out.printf("Out of range value: %d %n", value);
    }
   return kind;
}

Expressing this as a statement is roundabout, repetitive, and error-prone. To represent what you actually intended to express, you want to state that the code should compute a value of kind for each integer value passed to the method. It should be possible to say that directly.

By using a clearer and safer switch expression, you can rewrite the previous method:

jshell>var value = 3;
value ==> 3

jshell>var kind = switch(value){
   ...> case 0 -> "Zero";
   ...> case 1, 3, 5, 7, 9 -> "Odd";
   ...> case 2, 4, 6, 8, 10 -> "Even";
   ...> default -> throw new IllegalStateException("Out of range value: " + value);
   ...> };

kind ==> "Odd"

There is a second form of switch expression. A switch expression can, like a switch statement, also use a traditional switch block with a case L: label that implies fall-through semantics. In this case, values would be yielded using the break with a value statement. The previous method could be rewritten as:

jshell>var kind = switch(value){
   ...> case 0:break "Zero";
   ...> case 1, 3, 5, 7, 9: break "Odd";
   ...> case 2, 4, 6, 8, 10: break "Even";
   ...> default: throw new IllegalStateException("Out of range value: " + value);
   ...> };

kind ==> "Odd"

The two forms of break (with and without value) are analogous to the two forms of return in methods.

When you write a switch expression, most of the time you will have a single expression to the right of the case L -> label. But there is a situation when a full block is needed to contain some logic; when this happens, you need the break statement to take an argument, which becomes the value of the enclosing switch expression:

jshell>var day = "Monday"
day ==> "Monday"

jshell>var j = switch (day) {
   ...>     case "MONDAY"  -> "0";
   ...>     case "TUESDAY" -> "1";
   ...>     default      -> {
   ...>int k = day.length();
   ...>int result = k + 2;
   ...>         break result;
   ...>     }
   ...> };
j ==> 8

A switch expression is a poly expression, an expression that may have different types in different contexts and whose type may not be fixed and known at compile time. If the target type is known, this type is pushed down into each case arm; if not, a standalone type is computed by combining the types of each case arm.

Let’s analyze the previous switch expression. You will notice that there are two cases returning a String type, while the default case is returning an int type and it is valid since the target is of var type.

Since I have tested how it returns an int to varj variable, so now let’s run the same expression, but before running it, first change the day variable to "MONDAY":

jshell> day = "MONDAY"
day ==> "MONDAY"

jshell>var j = switch (day) {
   ...>     case "MONDAY"  -> "0";
   ...>     case "TUESDAY" -> "1";
   ...>     default      -> {
   ...>int k = day.length();
   ...>int result = k + 2;
   ...>         break result;
   ...>     }
   ...> };
j ==>"0"

The small modification will report a compilation error:

jshell>int j = switch (day) {
   ...>     case "MONDAY"  -> "0";
   ...>     case "TUESDAY" -> "1";
   ...>     default      -> {
   ...>int k = day.length();
   ...>int result = k + 2;
   ...>         break result;
   ...>     }
   ...> };
|  Error:
|  incompatible types: bad type in switch expression
|      java.lang.String cannot be converted to int
|      case "MONDAY"  -> "0";
|                        ^-^
|  Error:
|  incompatible types: bad type in switch expression
|      java.lang.String cannot be converted to int
|      case "TUESDAY" -> "1";
|                        ^-^

Did you notice what I changed?

New switch statement/expression gotchas

Now let’s cover how the new switch statement/expression should be written and which cases you want to try to avoid.

Switch statement’s break cannot return a value

As you know, the traditional switch statement does not return a value, therefore you will commit a compilation-time error if you attempt to have a break associated with a switch statement that is designated to return a value:

jshell> String getOddOrEvenNumber(int value){
   ...>var kind = "N/A";
   ...>     switch(value){
   ...>        case 0: break "Zero";
   ...>        case 1: break "Odd";
   ...>        case 2: break "Even";
   ...>        default: break "N/A";
   ...>     }
   ...>    return kind;
   ...> }
|  Error:
|  unexpected value break
|         case 0: break "Zero";
|                 ^-----------^
|  Error:
|  unexpected value break
|         case 1: break "Odd";
|                 ^----------^
|  Error:
|  unexpected value break
|         case 2: break "Even";
|                 ^-----------^
|  Error:
|  unexpected value break
|         default: break "N/A";
|                  ^----------^
|  Error:
|  unreachable statement
|     return kind;
|     ^----------^

A small fix here and this function will compile like a charm:

jshell> String getOddOrEvenNumber(int value){
   ...>var kind = switch(value){
   ...>        case 0: break "Zero";
   ...>        case 1: break "Odd";
   ...>        case 2: break "Even";
   ...>        default: break "N/A";
   ...>     };
   ...>    return kind;
   ...> }
|  created method getOddOrEvenNumber(int)

Switch statement’s arrow syntax only points to a statement

It is normal to use the arrow syntax with a switch expression to return a value, but this is not allowed in a traditional switch statement. In this case, the arrow syntax should point to a statement and not try to return a value. Attempting to do so will result in a compilation error:

jshell> String getOddOrEvenNumber(int value){
   ...>     String kind = "N/A";
   ...>     switch(value){
   ...>        case 0 -> "Zero";
   ...>        case 1 ->  "Odd";
   ...>        case 2 -> "Even";
   ...>        default -> "N/A";
   ...>     }
   ...>    return kind;
   ...> }
|  Error:
|  not a statement
|         case 0 -> "Zero";
|                   ^----^
|  Error:
|  not a statement
|         case 1 ->  "Odd";
|                    ^---^
|  Error:
|  not a statement
|         case 2 -> "Even";
|                   ^----^
|  Error:
|  not a statement
|         default -> "N/A";
|                    ^---^

jshell>

Arrow and colon/break mixing are not allowed

If you try to mix the arrow syntax and the traditional colon/break syntax, the javac compiler will stop you. You will get the following error message:

jshell>var j = switch (day) {
   ...>     case "MONDAY"  -> "0";
   ...>     case "TUESDAY": break "1";
   ...>     default      -> {
   ...>int k = day.length();
   ...>int result = k + 2;
   ...>         break result;
   ...>     }
   ...> };
|  Error:
|  different case kinds used in the switch
|      case "TUESDAY": break "1";
|      ^------------------------^

jshell>

All match cases possibilities must be specified in a switch expression

When using a switch expression, all switch cases should be covered for a specific test variable. If you don’t cover all of them, you will run into a compilation error:

jshell> String getOddOrEvenNumber(int value){
   ...>var kind = switch(value){
   ...>        case 0: break "Zero";
   ...>        case 1: break "Odd";
   ...>        case 2: break "Even";
   ...>     };
   ...>    return kind;
   ...> }
|  Error:
|  the switch expression does not cover all possible input values
|     var kind = switch(value){
|                ^-------------...

Covering all the cases is not an easy task and it can be an irritating one, but the fix is easy — just add the default case and it will compile like a charm. Give it a try with this code.

Summary

So far, you have learned about OpenJDK Projects, specifically Project Amber, pattern matching, and how JEP 354 has extended the switch statement (in Java 12) so that it can be used as either a statement or an expression and that both forms can use either for traditional or simplified scoping and control flow behavior. You’ve also gotten a glimpse at the most common mistakes developers will make when using the new switch statement/expression.

These changes will simplify everyday coding and prepare the way for the use of pattern matching in switch.