JVM Advent

The JVM Programming Advent Calendar

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;
         }
      };
  1. There is a syntax error
  2. There is an infinite loop
  3. n is set to 0
  4. n is set to 1

Author: Cay Horstmann

Next Post

Previous Post

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

© 2024 JVM Advent | Powered by steinhauer.software Logosteinhauer.software

Theme by Anders Norén