Switch Expressions

The switch statement has been part of Java since day one, inherited from C. It was clunky, error-prone (fall-through!), and verbose.

Java 14 standardized Switch Expressions, turning switch from a control flow statement into a value-producing expression.

1. The Problems with Old Switch

  1. Fall-through by default: Forgot a break? Bugs happen.
  2. Statement, not Expression: Can’t assign the result to a variable directly.
  3. Scope pollution: Variables defined in one case leak to others.
// The "Dark Ages"
String dayType;
switch (day) {
    case MONDAY:
    case FRIDAY:
        dayType = "Work"; // Repeated assignment
        break;            // Easy to forget!
    case SUNDAY:
        dayType = "Rest";
        break;
    default:
        dayType = "Unknown";
}

2. The New Arrow Syntax (->)

The arrow syntax -> executes only the code to its right. No fall-through. No break needed.

switch (day) {
    case MONDAY, FRIDAY -> System.out.println("Work");
    case SUNDAY         -> System.out.println("Rest");
    default             -> System.out.println("Unknown");
}

3. Switch as an Expression

You can now capture the result of a switch directly into a variable.

// Clean & Concise
String dayType = switch (day) {
    case MONDAY, FRIDAY -> "Work";
    case SUNDAY         -> "Rest";
    default             -> "Unknown";
};

Exhaustiveness Checking

When used as an expression, the compiler ensures you cover all possible values.

  • For enums: You must cover all constants (or have default).
  • For sealed classes: You must cover all permitted subclasses.

If you miss a case, the code will not compile. This is a huge win for safety.


4. Hardware Reality: Under the Hood

How does switch compare to if-else?

Bytecode: tableswitch vs lookupswitch

When you compile a switch statement, the JVM chooses between two opcodes:

  1. tableswitch (O(1)): Used when case values are dense (e.g., 1, 2, 3). It creates a jump table (an array of memory addresses). The JVM just calculates offset + value and jumps. It’s instantaneous.
  2. lookupswitch (O(log n)): Used when values are sparse (e.g., 1, 1000, 50000). It performs a binary search on the sorted keys.

If-Else Chains are always O(N) (linear scan).

[!NOTE] Modern Switch Expressions use invokedynamic to bootstrap the logic, especially for Pattern Matching, allowing the JVM to optimize the dispatch strategy at runtime based on the actual data distribution.


5. The yield Keyword

Sometimes a single expression isn’t enough. You need a code block { ... } to calculate the result. In these blocks, use yield to return the value.

int result = switch (mode) {
    case "A" -> 1;
    case "B" -> 2;
    case "C" -> {
        System.out.println("Calculating C..."); // Side effect
        int calculated = 1 + 2;
        yield calculated; // Return value
    }
    default -> 0;
};

[!NOTE] yield is a context-sensitive keyword. You can still have a variable named yield elsewhere (though you shouldn’t!).


6. Interactive Demo: Refactoring to Modern Switch

Click the buttons to refactor the legacy code step-by-step.

int days = 0; switch (month) { case JAN: case MAR: days = 31; break; case APR: days = 30; break; default: throw new Exception(); }

7. Best Practices

  1. Prefer Arrow Syntax: It’s safer and cleaner.
  2. Use as Expression: Assign the result or return it directly.
  3. Avoid Default with Enums: If you cover all enum constants, omit default. That way, if you add a new enum constant later, the compiler will warn you!
// Good: Compile error if 'WEDNESDAY' is added to Day enum
String type = switch (day) {
    case MONDAY, TUESDAY, THURSDAY, FRIDAY -> "Work";
    case SATURDAY, SUNDAY                  -> "Weekend";
};