Java is getting a multi-way selection expression. Check out the current design, and join the bikeshedding.
JEP 325: Switch Expressions makes the case (no pun intended) for an expression-level version of the switch
statement. Suppose you want to obtain the number of letters in English weekday names, as provided by an enumerated type, wouldn't you want to write
int numLetters = switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> 9; };
instead of
int numLetters = day == MONDAY || day == FRIDAY || day == SUNDAY ? 6 : day == TUESDAY ? 7 : day == THURSDAY || day == SATURDAY ? 8 : day == WEDNESDAY ? 9 : /* this can't happen */ -1;
or
int numLetters = -1; 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 = 7; }
Sure, what's not to like?
JEP 305 will provide type test and deconstruction patterns for switch statements, and we'd like those in expressions as well.
You can see the details of the current proposal here. The ->
token indicates that the expression that follows is the value of the switch
expression when the case
condition matches.
Note, by the way, that the case
condition is improved over the classic version where you had to say case MONDAY: case FRIDAY: case SUNDAY:
. Now you can use a comma-separated list case MONDAY, FRIDAY, SUNDAY
. That will make its way to switch statements as well.
The point of a switch expression is that it's an expression, not a statement. It has a value that you can assign to a variable or pass to a method. A switch expression relates to the switch statement as the ? :
expression does to the if else
statement.
In many programming languages, every statement is an expression (i.e. something with a value). For example, in Scala, you can write
val absValue = if (x >= 0) x else -x;
There is no need for distinguishing between an if else
statement and a ? :
expression.
Java is not one of those languages.
And here is where the separation between statements and expressions gets us in trouble. Let's say that we want to do something else before yielding value in a case branch:
int numLetters = switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> logger.info("Oh my, it's Tuesday"), 7; // Not the actual syntax case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> 9; };
To understand the proposed solution, we need to get into the weeds of the switch
statement that Java inherits from C. That statement is a dumpster fire. Each case
label is tried in turn, and as soon as one of them matches, execution jumps there. If there is no intervening break
, execution falls through to the next branch. Nobody in 1970 would have designed anything like that, when saner alternatives were widely known, right? Except, they did.
Fortunately, expression switch is designed not to be like that at all. All the branches must be disjoint, and there is no fall through.
There is just a tiny, teensy issue. Java, unlike many programming languages, has no way of forming an expression of the form
expression1, expression2, ..., expressionn
whose value is the value of the last expression. (Such an expression is called a block expresssion.) In C, you have the comma operator for that. Java chose not to pick up the comma operator as a general construct, but it allows a comma-separated list of expressions in the update part of a for
loop.
Could we allow commas in a switch expression as they are allowed in a for
statement (and in the preceding example code)? Sure. But that's not what is currently contemplated. Could we get a general purpose block expression in Java? Sure (with some caveats). But that's not what is currently contemplated either.
Instead, this is how you are supposed to do it:
int numLetters = switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY: logger.info("Oh my, it's Tuesday"); break 7; case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> 9; };
Ok, I get it. break 7
is like return 7
, except it returns the value 7 to the switch
expression, not a method. Just like break
returns from a switch
statement, in the same way that return
returns from a void
method.
And the :
instead of the ->
after the case
is meant to alert me to the fact that what follows are statements, not an expression.
That doesn't mean I like it. Look at a similar situation. Say you have a lambda expression
Predicate<String> longString = s -> s.length() > 12;
And now you feel the urge to add a logging statement.
Predicate<String> longString = s -> { logger.info("Oh my, " + s + " is long."); return s.length() > 12; };
It's the same transformation—adding a side effect. But the syntax is completely different—break
vs. return
, :
vs. ->
, no braces vs. braces. Two entirely different ways for achieving the exact same thing—another dumpster fire.
How do these design decisions get made? Is there an explicit list of requirements? I don't see that. JEP 325 states, without evidence, that “many existing switch statements are essentially simulations of switch expressions, where each arm either assigns to a common target variable or returns a value.” Looking at a random sample of switch statements in the JDK, most of them did something other than assign to a single variable. But some did just that. The JDK source has a switch
for approximately every thousand lines of code, so, by some definition of “many”, this may be a problem worth solving.
There is published experience that the fall-through behavior of branches in a switch
statement is (a) almost never useful and (b) error-prone. Disallowing fall-through in a switch
expression seems excellent progress. This means that the control flow of a switch
expression is different from a switch
statement. It would seem wise to be able to tell them apart at a glance. Those arrows might not be enough. It might not even be a good idea to call it a switch
expression. A case
expression might be better because it focuses attention on the similarities—the case matching—rather than the switching behavior.
Is there a pressing demand for having multiple statements in a case branch? On the Project Amber mailing list, Brian Goetz claims that “we’d get laughed out of the room” if that feature wasn't provided. Is there evidence for that? Has anyone ever complained about the fact that an ? :
expression has the same limitation? At any rate, this is an implicit assumption that should be made explicit. The use case of a logging statement can clearly be met by having a comma-separated expression list. Is there evidence for additional use cases?
There is also an implicit assumption that block expressions are unsuitable for Java. I am not sure where that comes from—after all, Java will soon have one. It's just not very pretty:
switch (0) { default: statements; break expression; }
We do know that programmers have trouble with break
in C. There is confusion about break
in switch
, while
, and if
statements. Java doesn't help by adding a labeled break
. Adding another wrinkle to break
seems like a poor idea to me. Except for puzzler writers and book authors, of course.
Then again, what do I know. I didn't do formal user studies on this. Neither did you, right? And yet, I bet you have an opinion. Join the bikeshedding!
Comments powered by Talkyard.