Java Switch
You already know how to shape the control flow of a program using if-else statements. Perhaps, you have faced situations in which you had to stack and nest multiple if-else statements to get the desired result. In this topic, you will learn an alternative way to deal with multiple choices: the switch statement. It enhances code readability, efficiency, and organization compared to lengthy if-else
chains, making it valuable for handling multiple conditions.
When a conditional statement is not so good
Suppose you need to write a program that performs different actions depending on the value of a variable. For example, choosing an action from the menu of a game. To do that you can use a conditional statement with multiple branches as shown below.
int action = ...; // a certain value from 1 to 4
if (action == 1) {
System.out.println("Starting a new game...");
} else if (action == 2) {
System.out.println("Loading a saved game...");
} else if (action == 3) {
System.out.println("Displaying help...");
} else if (action == 4) {
System.out.println("Exiting...");
} else {
System.out.println("Unsuitable action, please try again.");
}
Of course, this code handles the task. But if your conditional statement has a lot of branches, it can negatively impact your code's readability.
Three keywords: switch, case, and default
The switch statement provides a way to choose between multiple cases based on the value of a single variable (not an expression!). The variable can be an integer number, character, string, or enumeration. The last two types will be examined further in future topics.
With the switch statement, the previous code will look like this:
switch (action) {
case 1:
System.out.println("Starting a new game...");
break;
case 2:
System.out.println("Loading a saved game");
break;
case 3:
System.out.println("Displaying help...");
break;
case 4:
System.out.println("Exiting...");
break;
default:
System.out.println("Unsuitable action, please, try again");
}
As you can see, this code is well-structured and easier to read than the equivalent conditional statement. We have not explained the keywords switch
, case
and break
yet, but you can already guess what they mean.
The general form of the switch statement
Here's the most general form of the switch statement:
switch (variable) {
case value1:
// do something here
break;
case value2:
// do something here
break;
//... other cases
case valueN:
// do something here
break;
default:
// do something by default
break; // it can be omitted
}
The switch
and case
keywords are always required here. The keywords break
and default
are optional. The keyword break
stops the execution of the whole switch statement, not just one case.
If a case
does not have the break
keyword, the following case
will be executed as well, including the default
case. The default
case is also executed if there's no other case
that matches the variable value. The break
keyword in the default
branch is optional and can be omitted.
A case
section may contain any block of code, even a nested switch
statement. However, it is recommended to avoid deeply nested code structures whenever possible.
An example with "zero", "one", and "two"
Let's consider another example. The following code outputs the names of integer numbers or a default text. This switch statement has three base cases and a single default case.
int val = ...;
switch (val) {
case 0:
System.out.println("zero");
break;
case 1:
System.out.println("one");
break;
case 2:
System.out.println("two");
break;
default:
System.out.println("The value is less than zero or greater than two");
}
If val
is 0, the code prints:
zero
If val
is 1, the code prints:
one
if val
is 10, the code prints:
The value is less than zero or greater than two
If you forget the keyword break
in a case, the compiler won't consider it an error. Let's remove it from the second case (i.e. case 1
) and assign 1
to val
. The program prints:
one
two
Omitting the break
keyword is not a good practice. Try to avoid it.
When you have a limited number of cases to choose from, switch statements can help you avoid extensive if-else constructions. For that, you need the switch
keyword to introduce the value to evaluate, and case
for each of the possible values. Do not forget to also use the break
keyword to avoid executing extra cases and default
branch to indicate the default behavior.
Java 12 introduced some new features which allows you to use switch as an expression. This can be used to simplify many switch statements. Switch statements are often used to avoid long chains of if
and else if
statements, generally making your code more readable. That being said, switch statements can be verbose in their own way, and the strict requirements for placing break
statements often make them error-prone. Switch expressions were designed to offer a more concise and less error-prone alternative to switch statements.
Switch statements vs switch expressions
The main difference between a switch expression and a switch statement is that while a switch statement can be used to update the value of a predefined variable, a switch expression is assigned to a variable. This is possible because a switch expression evaluates to a specific value. Additionally, switch expressions introduced a new arrow syntax that condenses the code, makes it more readable, and eliminates the need for break
statements. Let's look at an example that demonstrates these differences.
We begin with an enumeration of various things that are going to be taste-tested. Our switch statement and switch expressions are going to assign a taste rating as an integer from 1 to 10, with 1 being utterly disgusting and 10 being absolutely delicious. First, we'll look at how this would commonly be written as a switch statement:
private enum ThingsToTaste {PIZZA, BROCCOLI, STEAK, SUGAR, DIRT, MEATBALLS, CHOCOLATE}
int tasteValue = 0;
ThingsToTaste taste = ThingsToTaste.DIRT;
switch (taste) {
case SUGAR:
case PIZZA:
case CHOCOLATE:
tasteValue = 10;
break;
case MEATBALLS:
case STEAK:
tasteValue = 7;
break;
case BROCCOLI:
tasteValue = 4;
break;
case DIRT:
tasteValue = 1;
break;
default:
throw new IllegalStateException("Invalid tastable object: " + taste);
}
System.out.println(taste + ": " + tasteValue);
Now let's contrast this with a switch expression.
If you have IntelliJ set to use Java 12 or higher, it will convert your switch statement into a switch expression with just one click.
int tasteValue = switch (taste) {
case SUGAR, PIZZA, CHOCOLATE -> 10;
case MEATBALLS, STEAK -> 7;
case BROCCOLI -> 4;
case DIRT -> 1;
default -> throw new IllegalStateException("Invalid tastable object: " + taste);
};
As you can see, this is way shorter. Let's go through the changes! First, the tasteValue
variable did not have to be initialized before the switch expression. Instead, the entire switch expression is assigned to be the value of tasteValue
. This works because the switch expression will ultimately yield an integer value. The next major difference is that when multiple case
statements yield the same value, they can all be combined into one line. SUGAR
, PIZZA
, and CHOCOLATE
all yield 10, so we can simply write case SUGAR, PIZZA, CHOCOLATE -> 10;
.
Next, note that the break
statements are gone! The new arrow syntax replaces the need for both the :
after case
and the break
at the end of the case statement. The arrow signals that once the value is reached, it is to be assigned to the tasteValue
variable and then stop. We no longer have to explicitly state the full assignment expression; just stating the value is enough. The JVM knows to set the integer value to tasteValue
.
We can still have a default
case at the end as a fallback option. Also notice that in this example the default
case does not return an integer, but instead throws an exception. In fact, there are three possibilities for what can come after the arrow:
- a value of the type the switch expression was declared with
- throw a new exception
- a code block that evaluates to a value of the correct type
One very important thing that you must keep in mind is that since switch expressions evaluate to a specific value of a specific type, you need to account for all possible cases.
If the data type is a primitive or an object, then you must provide a default
case. The only exception is using an enum
type because it is easier to account for every possibility.
Variations of switch expressions
You can also use colon case
statements in a switch expression. The only real difference in the code compared to a regular old switch
statement is that it is assigned to a variable and there are no break
statements. While this is a valid option, it is not preferable because it does not take advantage of the newer and more compact arrow syntax. It also makes it easier to lose track of whether you are looking at a switch
statement or a switch
expression because other than the variable assignment at the top and the absence of break
statements, there are no other differences.
Java 13 introduced the yield
keyword which can be used inside colon case
statements to identify the value the case
statement yields. It also replaces the break
statement and removes the need to explicitly mention the variable the value is assigned to. If you are going to use colons in your case
statements, using the new yield
keyword is the best option.
int tasteValue = switch (taste) {
case SUGAR:
case PIZZA:
case CHOCOLATE:
yield 10;
case MEATBALLS:
case STEAK:
yield 7;
case BROCCOLI:
yield 4;
case DIRT:
yield 1;
default:
throw new IllegalStateException("Invalid tastable object: " + taste);
};
The yield
keyword cannot be used inside a switch
statement. Likewise, break
cannot be used in a switch
expression. Therefore the use of yield
helps ensure that the reader of your code doesn't forget which type of switch
they are reading.
There is also an in-between option that uses the new arrow syntax but puts a code block with a yield
in it to the right of the arrow. This might seem unnecessarily verbose compared to the first example of the arrow case syntax shown above, but there are situations in which this long way has some advantages. The yield
statement must be the last line in the code block, but you can call other functions in the lines before it. A simple example of this would be printing the value about to be yielded to the console right before yielding it, as you can see in the example below.
tasteValue = switch (taste) {
case SUGAR, PIZZA, CHOCOLATE -> {
System.out.println(10);
yield 10;
}
case MEATBALLS, STEAK -> {
System.out.println(7);
yield 7;
}
case BROCCOLI -> {
System.out.println(4);
yield 4;
}
case DIRT -> {
System.out.println(1);
yield 1;
}
default -> {
throw new IllegalStateException("Invalid tastable object: " + taste);
}
};
A switch expression can be used instead of a switch statement to make the code more concise and less error-prone. The entire switch expression is assigned to a variable because it yields a value. Unless you are using an enum
type in your switch expression, you must include a default
case. You can yield a single value, throw an exception, or use a code block that ultimately evaluates to a single value.
In this topic, we looked at a few variations of switch
expression syntax. The new arrow syntax allows us to put all of our cases that yield the same result on one line; the arrow replaces the colon and the break
. Java 13 introduced the yield
keyword, which can be used in switch expressions but not in switch statements. It can be used with both the colon or the arrow syntax, but it is typically used at the end of a code block to return a value, often after calling other functions earlier in the block. If yield
is used with the colon syntax, it replaces the break
statement, just as the arrow does in the arrow syntax. An easy way to differentiate between switch expressions and switch statements is that switch expressions cannot have break
statements in them, and switch statements cannot have yield
statements in them.