Watercooler
March 19, 2021
6
min read

Modern Java Switch Expressions

Michael Inden

In this blog, I will cover some improvements of switch. The old syntax dates back to the early day of Java, and its handling is a bit cumbersome.

Let's look at the newer, slightly modified syntax for switch, called Switch Expressions. With it, case distinctions can be formulated much more elegantly than before.

Introductory example

To demonstrate switch expressions, I resort to mapping days of the week to their textual length as an example.

To better understand the need for this syntax improvement, let’s first examine how this task would have been formulated with the old syntax. Afterwards, we’ll look at the advantages provided by the new syntax of switch.

Analysis: What were some weaknesses of the switch so far?

Let’s start with the implementation of mapping weekdays of type DayOfWeek to their textual length using the older syntax:

DayOfWeek day = DayOfWeek.FRIDAY;

int numOfLetters;
switch (day)
{
  case MONDAY:
  case FRIDAY:
  case SUNDAY:
    numOfLetters = 6;
    break;
  case TUESDAY:
    numOfLetters = 7;
    break;
  case THURSDAY:
  case SATURDAY:
    numOfLetters = 8;
    break;
  case WEDNESDAY:
    numOfLetters = 9;
    break;
  default:
    numOfLetters = -1;
}

Modern Switch Expressions

Let's have a critical look at the source code. First of all, the shown construct does not appear elegant and is also quite long. The multiple specifications of values need accustoming, too. Even worse, a break is needed so that the processing runs without surprise and there is no fall-through. Moreover, we need to set the (artificial) auxiliary variable numOfLetters correctly in each branch. In particular, despite the actually complete coverage of the enum values, the default is necessary. Otherwise, the compiler complains that the variable numOfLetters may not be initialized – unless you have already assigned a value to it initially. So how is it better?

Syntax of the new Switch Expressions

With the new "Switch Expressions", expressing case distinctions is made much easier and provides an intuitive notation:

DayOfWeek day = DayOfWeek.FRIDAY;

int numOfLetters = switch (day)
{
  case MONDAY, FRIDAY, SUNDAY -> 6;
  case TUESDAY                -> 7;
  case THURSDAY, SATURDAY     -> 8;
  case WEDNESDAY              -> 9;
};

From this example, we notice some syntactic innovations: In addition to the obvious arrow instead of the colon, multiple values can now be specified after the case. Conveniently, there is no more need for break: The statements after the arrow are only executed specifically for the case and no fall-through exists with this syntax. Besides, the switch can now return a value, which avoids the need to define auxiliary variables. Instead of just stating a value after the arrow, it is also possible to specify expressions such as assignments or method calls without any problems. And even better, of course, still without the need for a break. Furthermore, this is no longer allowed in the new syntax after the arrow. 

Special feature: completeness check 

In the old version of switch it was possible to omit the default or to specify a case for individual values, for example WEDNESDAY. The compiler wouldn't have recognized this in switch itself. This would only have been pointed out later when accessing the variable used in the example that it is not initialized in every case. 

The handling has conveniently improved with the new switch expressions: Let’s assume we did not specify WEDNESDAY in the above example. Then this is directly criticized by the compiler: "A switch expression should cover all possible values." Additionally, IDEs offer the choice of adding either the appropriate case or a default, but not both: full coverage of the enum values is automatically detected. This innovation is necessary because the switch must now return a value in each case.

While it is still quite clear for enums with a limited set of possible values, the following question arises: How does it work for other types, such as ints? In such cases, the compiler can only state that possibly not all values are covered, and complains: "A switch expression should cover all possible values." Then the IDEs suggest to add a default – here indicated by the line commented out:

String numericString = switch (value)

{
  case 1 -> "one";
  case 2 -> "two";
  case 3 -> "three";
  // default -> "N/A";
};

Other reasons for the innovation

Let me go into more detail about the completeness check of the value coverage in the cases because there are sometimes difficulties when using switch. 

Pitfall 1: Incomplete value specifications

With the Date and Time API, of course, the complicated construct could be simplified significantly: month.getDisplayName(TextStyle.FULL, Locale.UK).

In the following, we want to map a value from the enumeration java.time.Month to the corresponding month name. Conventionally, this can be solved somewhat as follows:

Month month = Month.JULY;

String monthAsString;
switch (month)
{
case JANUARY:
  monthAsString = "January";
  break;
case FEBRUARY:
  monthAsString = "February";
  break;
case MARCH:
  monthAsString = "March";
  break;
//...
default:
  monthAsString = "N/A";
}

Again, the shown construct is not really elegant. Depending on whether a case is de fined for the value Month.JULY or not, one gets the value "July" or "N/A". Also, a break is needed to ensure that the processing is performed without surprise. 

Pitfall 2: Fall-through and default between the breaks

Let’s consider something rather clumsy, namely the default in the middle of the cases and without a break:

// ATTENTION: Sometimes very bad error: default in the middle of the cases
Month month = Month.JULY;

String monthAsString;
switch (month)
{
  case JANUARY:
    monthAsString = "January";
    break;
  default:
    monthAsString = "N/A"; // fall-through happens here
  case FEBRUARY:
    monthAsString = "February";
    break;
  case MARCH:
    monthAsString = "March";
    break;
}

The input Month.FEBRUARY results in the value "February" as expected, but surprisingly this also happens for the input Month.JULY. Why is this? First, because of the cases unlisted value JULY, the default branch is executed. Because of the missing break, a fall-through is also (unexpectedly) triggered. This causes the code for case FEBRUARY to be executed, which can be quite confusing. 

Remedy with the new switch expressions

With the help of the new syntax, the whole thing is much easier to write as follows: 

static String monthToName(final Month month)
{
  return switch (month)
  {
    case JANUARY -> "January";
    default -> "N/A"; // NO fall-through here
    case FEBRUARY -> "February";
    case MARCH -> "March";
  };
}

It is especially worth mentioning that one can directly return the value calculated by the switch construct. Furthermore, the fall-through does not occur. Thus the input Month.FEBRUARY returns "February" as expected, and moreover the default in the middle of the cases is not quite as dramatic, though certainly not pretty either. Unlike the old syntax, the input Month.JULY just no longer results in an unexpected output "February", but as specified by default in "N/A". Furthermore, if there were a case JULY, it would always be executed, regardless of the position of the default, thus analogous to the behavior of the previous switch statement.

Conclusion 

The Java releases up to and including 13 are rather manageable in terms of their innovations. This is true even for Java 11 as an LTS version. Fortunately, Java 14 brings a good slew of useful enhancements: On the one hand, there are the convenient syntax changes in switch. Another is the multi-line strings, called Text Blocks. These are covered in one of the next blogs. 

Let me conclude: The new syntax of switch seems to be just a small change, but it has an enormous effect on readability and ease of use. This blog introduced the benefits and simplifications of the new syntax of switch so that you can profit from it in your own projects.

Modern Java Switch Expressions

March 19, 2021
6
min read

Subscribe to DevDigest

Get a weekly, curated and easy to digest email with everything that matters in the developer world.

Learn more

From developers. For developers.