Java Flow Control: The switch Statement

Introduction

Conditional statements and loops are a very important tool in programming. There aren't many things we could do with code that can only execute line-by-line.

That's what "flow control" means - guiding the execution of our program, instead of letting it execute line-by-line regardless of any internal or external factors. Every programming language supports some form of flow control, if not explicitly via ifs and fors or similar statements - then it implicitly gives us the tools to create such constructs, i.e. low-level programming languages usually achieve that affect with a lot of go-to commands.

Loops were a concept used long before computer programming was even a thing, but the first person to use a software loop was Ada Lovelace, commonly known by her maiden name - Byron, while calculating Bernoulli numbers, back in the 19th century.

In Java, there are several ways to control the flow of the code:

  • if and if-else statements
  • switch statements
  • while and do-while statements
  • for and enhanced for statements
  • break and continue statements

The switch Statement

If we wish to compare a value to multiple values and execute code based on their equality, we could do something along the lines of:

String input = "someCommand";

if (input.equals("date")) {
    System.out.println("The current date is: " + new Date());
} else if (input.equals("help")) {
    System.out.println("The possible commands are...");
} else if (input.equals("list")) {
    System.out.println("The files in this directory are...");
} else if (input.equals("exit")) {
    System.out.println("Exiting application...");
} else {
    System.out.println("Command Not Supported");
}

This can quickly become cumbersome and unreadable. The switch statement was introduced to us precisely to avoid this whenever possible. This statement is used when we have several different things that we want executed based on the value of a single expression:

switch(variable) { 
    case constant1:
        // Do something if the variable is equal to constant1. 
        // constant1 must be of same type as variable
        // or easily converted to, such as Integer -> int
        break; 
    case constant2:
        // Some code
        break; 
    ... 
    default: 
        // Some code
        break;
}

The variable passed as the switch argument is the variable or expression whose value we are comparing. That value is compared to each of the case values. If the variable we're checking matches any of the cases, the code following that case is executed. We can have as many case statements as we want.

There are four new keywords we're introduced to here: switch, case, break, and default.

switch

The switch statement typically accepts a variable, though it can also accept an expression if it returns an accepted type:

// Comparing the value 5 with case values
switch(5) {
    // Cases
}

int x = 5;
int y = 10;

// Comparing the value 15 with case values
switch(x+y) {
    // Cases
}

// Booleans are not supported by switch statements, 
// so this won't compile
switch(!true) {
    // Cases
}

If we pass a null value to the switch, a NullPointerException will arise.

case

The case label's value must be a compile-time constant. This means that for all case values, we must either use literals/constants (i.e. "abc", 5, etc.) or variables that have been declared final and assigned a value:

final int i = 5;
int y = 15;
final int z;
z = 25;

int x = 10;
switch(x) {
    case i:
        // i can't be changed at any point due to the
        // `final` modifier, will compile
        break;
    case y:
        // Won't compile as `y` isn't a compile-time constant
        break;
    case 10+10:
        // Compiles, as 10+10 is a compile-time constant
        break;
    case z:
        // Doesn't compile as z wasn't initialized with
        // its declaration, thus it isn't considered to
        // be a compile-time constant
        break;
    case null:
        // Doesn't compile, there can't be a null case
        break;
}

The cases must all be unique, otherwise Java the code won't compile. If the cases weren't unique, there would be no way to know which case to execute among the duplicate values.

break

The break keyword is used to break the flow of the code within cases. Once the switch statement has found a case that matches the passed variable, it proceeds to execute case code until either the first break keyword is encountered or the end of the switch block itself.

For example:

int ourNumber = 1;

switch(ourNumber) {
    case 1:
        System.out.println("One");
    case 2:
        System.out.println("Two");
    case 3:
        System.out.println("Three");
        break;
    case 4:
        System.out.println("Four");
}

The output of this code is:

One
Two
Three

In this example, the flow of execution "falls through" from the first case statement (which matched the ourNumber value) to the break statement.

This can sometimes lead to unwanted cases being executed, so we should be careful to add break statements, and a good idea is to add them by default in every case, and remove them later if we do want to execute multiple cases for some inputs. More info on the break statement can be found here.

If we did want the same code executed for multiple cases, this is a common pattern used:

...
case "a":
case "b":
    System.out.println("Variable is equal to constant1 or constant2!");
    break;
...

This would execute the System.out.println line if our variable is equal to either "a" or if it's equal to "b".

default

The default case is a piece of code that switch executes if the variable we've passed to it doesn't match any of the given cases. This statement is optional, though highly recommended to avoid situations where user-defined input breaks the whole flow of the application.

Accepted Data Types

The variable passed as a switch argument can be one of the following:

  • char
  • byte
  • short
  • int
  • Integer
  • Short
  • Byte
  • Character
  • enum
  • String

This is a huge jump from the if and else-if statements, which only support boolean expressions.

That being said, we can easily rewrite the if/if-else example from the beginning of this article:

 switch("someCommand") {
     case "date":
         System.out.println("The current date is: " + new Date());
         break;
     case "help":
         System.out.println("The possible commands are...");
         break;
     case "list":
         System.out.println("The files in this directory are...");
         break;
     case "exit":
         System.out.println("Exiting application...");
         break;
     default:
         System.out.println("Command Not Supported");
         break;
 }

Or, as another example, we could pass an integer and replace this chain of if and else-if blocks with a more readable counterpart:

int ourNumber = 4;

if (ourNumber == 1) {
    System.out.println("One");
}
else if (ourNumber == 2) {
    System.out.println("Two");
}
else if (ourNumber == 3) {
    System.out.println("Three");
}
else if (ourNumber == 4) {
    System.out.println("Four");
}
else {
    System.out.println("Larger than 4");
}

The equivalent switch statement would look like the following:

switch(ourNumber) {
    case 1:
        System.out.println("One");
        break;
    case 2:
        System.out.println("Two");
        break;
    case 3:
        System.out.println("Three");
        break;
    case 4:
        System.out.println("Four");
        break;
    default:
        System.out.println("Larger than 4");
        break;
}

It's clear that if ourNumber is equal to 1, the code after case 1: will be executed, and "One" will be printed to the standard output.

Note: Unlike other data types, Strings aren't compared with the == operator, but with the .equals() method when checking against all the cases. This is done so that the actual value of the strings are compared, and not the memory location.

Nested switch Statements

We can nest multiple switch statements.

switch(var1) {
    case constant1:
        switch(var2) {
            // ...
        }
        break;
    case constant2:
    ...
}

Lambda Expressions

Java 12 has a new, more concise way of handling switch statements using Lambda Expressions.

By using switch statements in this way, breaks are no longer necessary, and returning a value is more readable. This is only available as a preview option currently. A typical new type of switch would look something like this:

switch(ourNumber) {
    case 7, 3, 8, 4 -> System.out.println("Very popular lucky number");
    case 5, 13, 9, 6 -> System.out.println("Kind of popular lucky number");
    default -> System.out.println("Not all that popular");
}

// Or something like:
String s = switch(ourNumber) {
    case 7, 3, 8, 4 -> "Very popular lucky number";
    // ...
}
System.out.println(s);

Conclusion

Flow control in code is essential for absolutely every application. Statements that alter the flow of code are fundamental building blocks and every aspiring developer should be completely in control/aware of how they work.