My last post "Playing with JDK 12's Switch Expressions" talked about use of the JDK 12 Early Access Builds to try out JEP 325 switch
expressions and statements and provided a simple example. This post uses the same JDK 12 Early Access Build 10 to demonstrate different features of switch
expressions and enhanced switch
statements.
I used a 2x2 grid in the blog post "Enhancing Java switch Statement with Introduction of switch Expression" to illustrate that the new "arrow" syntax ("switch labeled rule") can be used with a switch
statement or with a switch
expression per JEP 325. Similarly, per JEP 325, the traditional "colon" syntax ("switch labeled statement group") can also be used with either a switch
expression or with a switch
statement. In other words, presence of the colon (:
) does NOT necessarily imply a switch
statement and presence of an "arrow" (->
) does NOT necessarily imply a switch
expression. For convenience, I've included an adapted version of the table shown in my earlier post here.
|
STATEMENT ("Nonlocal control flow _out_ of a switch [continue to an enclosing loop,
break with label, return]") |
EXPRESSION (Totality: return a value) |
SWITCH LABELED STATEMENT GROUP ("Colon") (Enables Fall-through) |
switch we know and "love", but enhanced |
break returns a value like return |
SWITCH LABELED RULE ("Arrow") (Prevents Fall-through) |
"Syntactic shorthand" for Statement/Colon (above) plus
- "obviates the annoyance of 'break'"
- "implicitly prevents fallthrough of all forms"
- "avoids the confusion of current switch scoping"
|
Arrow (-> ) points to returned value |
With the JDK 12 Early Access Builds, it is convenient to try the new switch
expression out and we can also try out the traditional and enhanced versions of the switch
statement.
Traditional switch
Statement
The traditional switch
statement that we "know and love" is still available even with JDK 12 preview enabled (--enable-preview
). An example of this traditional switch
statement that compiles and executes successfully even with JDK 12 language feature preview enabled is shown next.
/**
* Demonstrate traditional switch statement assigned to
* local variable.
*/
public static void demonstrateTraditionalSwitchStatement()
{
out.println("Traditional Switch Statement:");
final int integer = 3;
String numericString;
switch (integer)
{
case 1 :
numericString = "one";
break;
case 2 :
numericString = "two";
break;
case 3:
numericString = "three";
break;
default:
numericString = "N/A";
}
out.println("\t" + integer + " ==> " + numericString);
}
This and all other code examples shown in this post are available on GitHub. This particular example shows a common use of the traditional switch
statement to set a local variable's value. I intentionally chose this use case because a new switch
expression is an improved approach to accomplishing this.
Enhanced switch
Statement
As stated previously, we can use the new "arrow" syntax ("switch labeled rules") with the enhanced switch
statement. This is shown in the next code example that compiles and runs against the JDK 12 Early Access Build 10 when --enalved-preview
is used.
/**
* Demonstrate enhanced switch statement used to assign
* a local variable.
*/
public static void demonstrateEnhancedSwitchStatement()
{
out.println("Enhanced Switch Statement:");
final int integer = 2;
String numericString;
switch (integer)
{
case 1 -> numericString = "one";
case 2 -> numericString = "two";
case 3 -> numericString = "three";
default -> numericString = "N/A";
}
out.println("\t" + integer + " ==> " + numericString);
}
This last examples shows the switch
still being used as a statement, but in this case it takes advantage of the "arrow" syntax ("label rules") to accomplish its switching without explicit specification of break
. This is not only less code, but more importantly has the advantage of not allowing for the often dreaded switch
"fall-through." In short, the enhanced switch
statement works like the current/traditional switch
statement, but without the potential warts of the traditional version.
New switch
Expression Returning Value via break
Beyond enhancing the current switch
statement to allow for specification of a switch
statement without risk of fall-through, JEP 325 also introduces the concept of using the switch
keyword in a switch
expression. The Java Tutorial's "Expressions, Statements, and Blocks" page explains the differences between statements and operations. For purposes of this discussion, two of the important observations made in that tutorial are (I added bold emphasis):
- "An expression is a construct made up of variables, operators, and method invocations ... that evaluates to a single value."
- "The Java programming language allows you to construct compound expressions from various smaller expressions as long as the data type required by one part of the expression matches the data type of the other."
The next code listing demonstrates how, with JDK 12 Early Access Build 10 and --enable-preview
, one can replace the code shown above that used a switch
statement to assign a value to an earlier declared local variable with a single statement that uses a switch
expression to assign its result value to the local variable in a single statement.
/**
* Demonstrate switch expression using colons and breaks.
*/
public static void demonstrateSwitchExpressionWithBreaks()
{
final int integer = 1;
out.println("Switch Expression with Colons/Breaks:");
final String numericString =
switch (integer)
{
case 1 :
break "uno";
case 2 :
break "dos";
case 3 :
break "tres";
default :
break "N/A";
};
out.println("\t" + integer + " ==> " + numericString);
}
The code example just shown demonstrates use of a switch
expression that looks very similar to the traditional switch
statement example shown earlier. However, there are a couple significant differences. One difference is that this switch
expression returns a result that is assigned to the local variable "numericString
". The second difference, which directly relates to the switch
expression being able to return a value, is that the break
clauses now each have the value to be returned for the relevant case
immediately specified after the break
keyword. In essence, the break
in the switch
expression acts like a Java method return
.
New switch
Expression Returning Value via Label Rules
The example just shown demonstrates that one can return a value from a switch
expression with similar colon (:
) and break
syntax to what one is likely used with switch
statements. Besides being familiar, the other advantage of this is that one can specify multiple statements to occur for a single case
before returning a single value. In most cases, however, it will likely become popular to return a value from a switch
expression using the "arrow" syntax discussed earlier to benefit from no risk of fall-through and to avoid scope surprises commonly associated with the traditional switch
statement. The next code listing demonstrates how the new switch
expression can use "label rules" ("arrow" syntax) instead of colon and break
to elegantly return a single resolved value for the switch
.
/**
* Demonstrate switch expressions using "arrow" syntax.
*/
public static void demonstrateSwitchExpressionWithArrows()
{
final int integer = 4;
out.println("Switch Expression with Arrows:");
final String numericString =
switch (integer)
{
case 1 -> "uno";
case 2 -> "dos";
case 3 -> "tres";
case 4 -> "quatro";
default -> "N/A";
};
out.println("\t" + integer + " ==> " + numericString);
}
The four examples above demonstrate each of the cases shown in the 2x2 grid. The remainder of this post will discuss some additional observations from trying out switch
expressions and statements with JDK 12 Early Access Build 10.
Multiple Constants Can Be Specified for a Single case
Any of the four quadrants in the 2x2 grid allow for multiple constants to be associated with a single case
. This is demonstrated in the next code listing that compiles and runs with JDK 12 Early Access Build 10 with "preview language features" enabled.
/**
* Demonstrate that multiple constants can be associated with
* a single {@code case} and used in conjunction with a
* {@code switch} expression that uses the "arrow" syntax.
*/
public static void demonstrateLabelRulesWithSharedCases()
{
final int integer = 7;
out.println("Multiple Case Labels:");
final String numericString =
switch (integer)
{
case 0 -> "zero";
case 1, 3, 5, 7, 9 -> "odd";
case 2, 4, 6, 8, 10 -> "even";
default -> "N/A";
};
out.println("\t" + integer + " ==> " + numericString);
}
/**
* Demonstrate that multiple constants can be associated with
* a single {@code case} and used in conjunction with a
* {@code switch} statement that uses the traditional colon and
* {@code break} syntax.
*/
public static void demonstrateBlockedStatementsWithSharedCases()
{
final int integer = 6;
out.println("Multiple Case Labels:");
String numericString;
switch (integer)
{
case 0:
numericString = "zero";
break;
case 1, 3, 5, 7, 9:
numericString = "odd";
break;
case 2, 4, 6, 8, 10:
numericString = "even";
break;
default:
numericString = "N/A";
};
out.println("\t" + integer + " ==> " + numericString);
}
"Arrow" ("label rules") and Colon/break
("statement group") Cannot Be Mixed
The JDK 12 Early Access Build 10 compiler (javac
) does NOT allow the mixing of the "arrow" syntax and the traditional colon/break
syntax. Attempting to mix these results in the error message: "error: different kinds used in the switch". An example of code that would not compile and would show this particular error message is shown next.
/**
* WARNING - This does NOT compile, even with JDK 12 Early
* Access Builds and --enable-preview because JEP 325 does
* not allow the "arrow" syntax to be mixed with the
* traditional colon/break syntax.
*/
public static void demonstrateMixed()
{
final int integer = 3;
String numericString;
switch(integer)
{
case 1 :
numericString = "one";
break;
case 2 -> numericString = "two";
default -> numericString = "N/A";
}
return numericString;
}
switch
Statement's break
Cannot Return Value
The new switch
expression returns a value and when the colon and break
approach are used by the switch
expression, that returned value is designated immediately following the break
keyword. Because the traditional switch
statement does not return a value, it is a compile-time error to attempt to have a break
associated with a switch
statement designate a return value. The error ("error: unexpected value break") can be reproduced with the following code.
/**
* WARNING - This does NOT compile, even with JDK 12 Early
* Access Builds and --enable-preview because it is
* nonsensical to have a "statement" return a value; that
* is what an expression should be used for.
*/
public static void demonstrateSwitchStatementReturnedLabel()
{
final int integer = 4;
switch (integer)
{
case 1:
break "one";
case 2:
break "two";
case 3:
break "three";
default:
break "N/A";
};
}
When one attempts to compile the above code using JDK 12 Early Access Build 10's javac
compiler with flags --enable-preview
and -release 12
specified, four instances (corresponding to the three case
plus one default
) of the error message "error: unexpected value break" are seen. Not surprisingly, the simple change of assigning this switch
to a local variable (and effectively turning the statement into an expression) allows this code to compile. In other words, changing the code above to the code in the next code listing allows it to compile and run successfully.
/**
* This demonstrates that a {@code switch} "expression" is
* able to (and expected to) provide the "return" value for
* a given {@code case} and {@code default} instead of being
* a compiler error as it was for the "statement" example
* demonstrated in method
* {@link #demonstrateSwitchStatementReturnedLabel()}.
*/
public static void demonstrateSwitchExpressReturnedLabel()
{
final int integer = 4;
final String numericString =
switch (integer)
{
case 1:
break "one";
case 2:
break "two";
case 3:
break "three";
default:
break "N/A";
};
}
The current JEP 325 text includes a discussion on how this break
behavior is similar to methods' return
. That discussion points out that the switch
statement requiring no returned value after its break
s is analogous to a method returning void
. A switch
expression is expected to return a non-void
value.
switch
Statement's "Arrow" Syntax Must Point to a Statement
The following code will not compile with JDK 12 Early Access Build 10 even with --enable-preview
and -release 12
provided to the javac
compiler.
/**
* WARNING - This does not compile, even with JDK 12 Early
* Access Builds and --enable-preview and reports error message
* "error: not a statement" because it is expecting a
* {@code switch} "statement" but what is being provided to each
* {@code case} is NOT a statement.
*/
public static void demonstrateSwitchStatementReturnedValueViaLabelRule()
{
final int integer = 5;
switch (integer)
{
case 1 -> "one";
case 2 -> "two";
};
out.println(numericString);
}
The above code does not compile and the error message reported is, "error: not a statement". This is because the switch
is being used as a statement in this example, but the "arrow" syntax is "pointing" to literal strings rather than to a valid Java statement.
All Possibilities Must be Specified in a switch
Expression
Because a switch
expression needs to return a non-void
value, a switch
expression must specify a case
for all possible values it might switch on. In practice, this is likely to be accomplished via a default
to catch all possibilities not explicitly specified with case
. With a traditional switch
statement, it was not required to ensure that all possible values being switched on were covered by a case
or default
and that led sometimes to conditions such as I described in the blog post "Log Unexpected Switch Options".
The following code violates the rule that a switch
expression must specify all possible values in either a case
or via default
:
/**
* WARNING - This method will not compile even with JDK 12
* Early Access Build 10 with --enable-preview because of
* error; "the switch expression does not cover all possible
* input values".
*/
public static void demonstrateLackingCaseInSwitchExpression()
{
final int integer = 5;
String numericString =
switch (integer)
{
case 1 -> "one";
case 2 -> "two";
};
out.println(numericString);
}
The code just shown will not compile and the causal error message is, "error: the switch expression does not cover all possible input values."
The Effect of JEP 325 on Future Use of switch
Considering the possibilities presented by the availability of switch
expressions in Java being available in addition to switch
statements, and considering the advantages offered by the new "arrow" syntax that can be used with switch
expressions or statements, it is interesting to begin thinking about when each quadrant in the above 2x2 grid is most beneficial. In general, I believe I will find myself using the switch
expression with "arrow" syntax ("label rules") most often with enhanced switch
statements using "arrow" syntax also being frequently used. I suspect I'll use the traditional :
(break
) syntax far less often in the future. Even when I have multiple statements to be executed for a particular case
, I'll likely factor those statements into a single method that can be called in the case
using the "arrow" syntax. This will allow me to benefit from more obvious scoping and avoid the risks of fall-through. Given the ability to specify multiple constants for a single case
that will now be available, fall-through won't be necessary anymore even in cases where multiple cases lead to the same result.
Additional Resources