Switch expression

MAADA LoukmaneMAADA Loukmane
6 min read

The introduction of switch expressions in Java marks a significant evolution in the language, overcoming several limitations of the traditional switch statement and adding new capabilities, such as pattern matching and better handling of inheritance. This article compares the old switch statement to the new switch expression, showcases the benefits of the latter, and demonstrates how these changes can be applied to make for effective real-world scenarios concerning pattern matching and class hierarchies.

The Old Switch Statement

In Java, the switch statement has long been a fundamental control flow structure for conditional branching. Introduced in the earliest versions of Java, it allowed developers to write code that would execute different branches based on the value of a variable, typically an integer, enum, or string in newer versions.

Let’s say we have an int day that represents the days of the week, 1 being Sunday and 7 being Saturday. We wish to print the day based on that int, or throw an exception if the day is invalid.

Using the traditional switch statement, we get something like this :

void printDayFromInt(int day){
String dayName;

switch (day) {
    case 1:
        dayName = "Sunday";
        break;
    case 2:
        dayName = "Monday";
        break;
    case 3:
        dayName = "Tuesday";
        break;
    case 4:
        dayName = "Wednesday";
        break;
    case 5:
        dayName = "Thursday";
        break;
    case 6:
        dayName = "Friday";
        break;
    case 7:
        dayName = "Saturday";
        break;
    default:
        throw new IllegalStateException();
}
System.out.println(dayName);
}

Key Characteristics of the Old Switch:

  • Fall-through Behavior: Without a break statement at the end of each case block, the execution would fall through to the next case.

  • Limited Return Capability: The switch statement could not directly return a value. This forces us developers to store the results in variables and handle them outside the switch block.

  • Type Restrictions: Before Java 7, switch could only operate on primitive types (such as int, char, short), and Java 7 extended this to String. However, handling complex objects was still cumbersome.

  • Verbosity: Multiple break statements and duplicated case handling led to more verbose code that was prone to errors, such as missing breaks leading to unwanted fall-through.

2. Introducing the New Switch Expression

The traditional switch statement worked well for basic control flow, but it became clumsy for more complex conditions, particularly in cases where switches needed to return values. To address these limitations, Java 12 introduced switch expressions, which were further enhanced in Java 14 and fully integrated into Java 17.

Switch expressions bring several benefits:

  • Conciseness: The -> syntax eliminates the need for break statements.

  • Return Values: A switch expression can return a value directly, allowing more compact code.

  • Exhaustiveness: Switch expressions are exhaustive, meaning they must handle every possible input, either through individual cases or a default clause.

Here’s how the same example looks using a switch expression:

void printDayFromInt(int day){
String dayName = switch (day) {
    case 1 -> "Sunday";
    case 2 -> "Monday";
    case 3 -> "Tuesday";
    case 4 -> "Wednesday";
    case 5 -> "Thursday";
    case 6 -> "Friday";
    case 7 -> "Saturday";
    default -> "Invalid day";
};
System.out.println(dayName);
}

Key Improvements:

  • No Fall-through: The -> syntax inherently avoids fall-through, so no need for explicit break statements.

  • Returning Values: Switch expressions can return a value, which simplifies code significantly. In this example, the result is directly assigned to dayName.

  • Multiple Labels: Multiple cases can be combined in a single line using a comma separator.

For instance:

String typeOfDay = switch (day) {
    case 1, 7 -> "Weekend";
    default -> "Weekday";
};

3. Pattern Matching in Switch Expressions (Java 17)

One of the most exciting features introduced in Java 17 is pattern matching in switch expressions. This enhancement allows developers to handle different types of objects directly within a switch expression, eliminating the need for extensive instanceof checks and casting.

In earlier versions of Java, checking for object types in a class hierarchy often required nested if-else or switch blocks, which made the code cumbersome and harder to maintain. Pattern matching in switch provides a more elegant solution.

Let’s consider this scenario, I have three types of days, WeekDays where the workHours are 8 and WeekEnds where workHours are 0. Let’s create an interface Days, that has a single method getWorkHours() and let WeekDays and WeekEnds implement that interface.

interface Days {
    int getWorkHours();
}

class WeekDays implements Days {
    @Override
    public int getWorkHours(){
        return 8;
    }
}

class WeekEnds implements Days {
    @Override
    public int getWorkHours(){
        return 0;
    }
}

Now let’s say we want to print whether the Day I have passed as parameter is a weekend or a weekday.


void printDayType(Day day){
    if(day instanceof WeekEnds){
        System.out.println("WeekEnds 💃");
    }else if(day instanceof WeekDays){
        System.out.println("WeekDays 👨‍💻");
    }
}

Now, let’s use pattern matching in the switch expression to handle the different day types, but first we need to help java understand that WeekDays and WeekEnds are the only classes that inherit from the Day class. To do that we use the permits, sealed and final key words. Let’s update our code.

sealed interface Days permits WeekDays, WeekEnds{
    int getWorkHours();
}

final class WeekDays implements Days {
    @Override
    public int getWorkHours(){
        return 8;
    }
}

final class WeekEnds implements Days {
    @Override
    public int getWorkHours(){
        return 0;
    }
}

void printDayType(Day day){
    switch (day) {
        case WeekEnds weekEnds -> System.out.println("WeekEnds 💃");
        case WeekDays weekDays -> System.out.println("WeekDays 👨‍💻");
    };
}

Benefits of Pattern Matching:

  • Eliminates Casting: The switch expression automatically binds the matched type to a variable (e.g., WeekEnds weekEnds), avoiding the need for manual casting.

  • More Readable Code: By directly handling types within the switch, the code becomes more concise and readable.

  • Safe and Exhaustive: Switch expressions with pattern matching enforce exhaustiveness, which means all possible cases must be handled, leading to safer and more reliable code.

4. Code safety first

Switch expressions in Java significantly enhance code safety by enforcing exhaustiveness. Unlike traditional switch statements, where a developer could forget to handle a specific case, switch expressions ensure that all possible cases are covered. If a case is missing, the compiler will flag it, preventing the code from running until the developer addresses the issue. This compile-time safety check helps to eliminate unhandled cases and ensures that the program behaves predictably in all scenarios. By catching potential errors early, switch expressions reduce runtime bugs, making your code more robust and reliable. Additionally, they promote clearer and more maintainable code, as each branch of logic must be deliberately defined. This feature aligns with the general shift in Java towards more functional, concise, and safer code practices, reducing the risk of unintended behavior or crashes caused by unanticipated inputs.

5. Conclusion

The evolution from the old switch statement to the new switch expression, along with pattern matching, is a big step forward for Java. These changes make code shorter and easier to read, while also adding more safety and flexibility, especially when dealing with complex data structures and class hierarchies.

Switch expressions do more than just make code look nicer. They help prevent errors, make code easier to maintain, and simplify things, especially with pattern matching and polymorphism. Java developers using newer versions should adopt these changes to write cleaner and more efficient code.

0
Subscribe to my newsletter

Read articles from MAADA Loukmane directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

MAADA Loukmane
MAADA Loukmane

Moroccan software developer, Java/Spring. Love to learn, eager to write.