Give Me a break, or: How to Make Awesome Puzzlers with Java 12
Java 12 provides, in experimental form, a switch
expression and new forms of the switch
and break
statements. There is a profusion of new syntax and semantics for constructs that may find little use—except, of course, for authors of puzzlers and certification exam questions for whom this is a wonderful gift. If you enjoy Java puzzlers and would perhaps like to create some yourself, read on.
The Java 12 Expression Switch
Java 12 introduces an expression switch—a version of switch
that is an expression, not a statement. Here is a simple example:
enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }; public static int numLetters(Day day) { return switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; default -> 9; }; }
That’s nice.
Note that this form of switch
is an expression. It has a value that, in this case, is the expression in the return
statement. You could also assign the switch
expression to a variable, pass it as a method argument, and do all the other things that you can do with expressions in Java.
This diagram explains it concisely:
Expression | Statement | |
Two-way branch | ? : | if /else |
Multi-way branch | switch | switch |
Would it have been more consistent to have an operator for a multi-way branch expression? Sure, but … insert evil laugh … this way we get to make better puzzlers!
Go ahead. Give it a try. Where can you use an expression? Inside a switch
statement of course.
switch (switch (...) { case ... -> ...; case ... -> ... }) { case ...: ...; case ...: ...; }
Fill in something amusing for the ...
and ask what the outcome is. Also toss in an option for “it won’t compile”. (That’s the answer. Note the missing semicolon in the second case
branch of the expression switch
.)
This expression switch
has a remarkable feature: no fallthrough. You don’t have to put a break
at the end of each case
branch.
That’s great—a missing break
is a common error. But it seems a step backwards for for puzzler makers.
Don’t despair. I am about to bring you good tidings.
Value Breaks
Suppose you want to log something in one of the branches.
case TUESDAY -> { logger.info("Belgium?"); 7 } // Not legal Java
That’s Scala syntax. In Scala, a block is an expression whose value is the last expression of the block. In this example, 7. But Java doesn’t have block expressions.
Java 12 (whose version number makes us think of the 12 nights of christmas), comes bearing a gift for puzzle makers: a new break
statement. Its purpose is to return a value out of a block in a case
branch:
case TUESDAY -> { logger.info("Belgium?"); break 7; }
By the way, the ->
was purposefully used to remind you of lambda expressions. In lambda expressions, you have a similar problem. Suppose you have a lambda expression that yields an expression.
Runnable task = () -> 42;
And now you want to add a logging call. You do something quite similar:
Expression | Statement | |
Lambda | Runnable r = () -> 42; | Runnable r = () -> { logger.log(...); return 42; }; |
case branch | case ... -> 42; | case ... -> { logger.log(...); break 42; } |
As an aside—eagle-eyed readers will notice that there are no terminal semicolons in one quadrant of this table. More puzzler material…
This break
statement really acts like return
. It can be nested inside another block, and it jumps outside, yielding the value.
case ... -> { if (n % 2 == 0) break 42; else { logger.log(...); break 21; } }
Except of course, in loops and switch
statements where there is already a different meaning for break
. For example, this is illegal:
case ... -> { for (int i = 0; i < a.length; i++) { if (a[i] == x) break i; // Error } break -1; }
Value break
is exactly like return
, except inside loops and switch
statements, where it is forbidden. Go ahead—make a puzzler out of that right now. You know you want to.
Labeled Breaks
Way back in 1995, Java 1.0 introduced innovations such as classes and interfaces, garbage collection, and Unicode strings, while sticking to the C syntax for control structures that was familiar to so many programmers. Except for one teensy change.
In Java, you can use a labeled break
to break out of nested loops and get to the end of the loop that has the matching label at the beginning. Like this:
int i = 0; int j = 0; found: while (i < a.length) { while (j < a[i].length) { if (a[i][j] == x) break found; j++; } i++; } // Execution continues here after break found;
Did you ever use this feature? Don’t worry if not. Few people have, outside certification exams.
What happens if you have a loop inside a case
with a break foo;
? It entirely depends. If foo
occurs as a label of an enclosing loop, then you have a labeled break
. If not, and foo
is a variable, then you have a value break
. What if you have both? That’s a syntax error.
Go ahead, make a puzzler out of that. You know you want to.
Arrow Switch Statements
Have another look at the expression switch
syntax. You can say
case MONDAY, FRIDAY, SUNDAY ->
instead of
case MONDAY: case FRIDAY: case SUNDAY:
That’s good—the alternative would have looked pretty weird:
case MONDAY -> case FRIDAY -> case SUNDAY -> // Just kidding
So much goodness in the expression switch
. No fallthrough. No need to repeat case
. The switch
statement is getting really envious.
So, the Java designers decided to be nice and allow it to partake in that goodness. You can now write:
switch (day) { case MONDAY, FRIDAY, SUNDAY -> // No repeating of case numLetters = 6; // No fallthrough after -> case TUESDAY -> { logger.info("Tuesday"); numLetters = 7; } case THURSDAY, SATURDAY -> numLetters = 8; default -> numLetters = 9; }
Naughty Switch Expressions
Now it is the expression switch
‘s turn to get envious. The switch
statement now has two forms: naughty (circa 1970) and nice (2018). What if the expression switch
wanted to be naughty, with fallthrough?
This is where the fallacy of 2 x 2 diagrams comes in:
Expression | Statement | |
No fallthrough | int numLetters = switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; default -> 9; }; | switch (day) { case MONDAY, FRIDAY, SUNDAY -> numLetters = 6; case TUESDAY -> { logger.info("Tuesday"); numLetters = 7; } case THURSDAY, SATURDAY -> numLetters = 8; default -> numLetters = 9; } |
Fallthrough | ??? | switch(day) { case MONDAY, FRIDAY, SUNDAY: numLetters = 6; break; case TUESDAY: logger.info("Tuesday"); numLetters = 7; break; case THURSDAY: logger.info("Thursday"); case SATURDAY: numLetters = 8; break; default: numLetters = 9; } |
Do we really need to fill in the missing quadrant?
Apparently yes.
int numLetters = switch(day) { case MONDAY, FRIDAY, SUNDAY: break 6; case TUESDAY: logger.info("Tuesday"); break 7; case THURSDAY: logger.info("Thursday"); // Fallthrough case SATURDAY: break 8; default: break 9; };
Can you mix case ...:
and case ... ->
in the same switch
? Sadly no. This was once considered, but the anti-puzzler lobby carried the day.
Can you do case MONDAY: case FRIDAY: case SUNDAY:
for the first branch? You can make a puzzler for that, but at that point, your audience probably lost the will to live.
A Pre-Christmas Puzzler For You
When I gave a presentation about all this, I knew I had to make a puzzler. Is this switch naughty or nice? What does it do?
int n = 0; n = switch (n) { case 0: n++; default: { O: while (n > 0) { if (n == 1) break O; n--; } if (n > 0) break n; else break 0; } };
- There is a syntax error
- There is an infinite loop
n
is set to 0n
is set to 1
Published on Java Code Geeks with permission by Cay Hortstmann , partner at our JCG program. See the original article here: Give Me a break, or: How to Make Awesome Puzzlers with Java 12 Opinions expressed by Java Code Geeks contributors are their own. |
Nice article! Minor correction: “Runnable task = () -> 42;” doesn’t compile since Runnable.run() has a void return type. You may want to change Runnable to IntSupplier (package java.util.function) or Callable (package java.util.concurrent)
Great article