A bit of theory

Imagine that you're a knight stopped at a fork in the road. If you go left, you will lose your horse. If you go right, you will gain knowledge. How would we represent this situation in code? You probably already know that we use constructs like if-then and if-then-else to make these decisions.
if (turn_left) {
    System.out.println("You will lose your horse");
}
if (turn_right) {
    System.out.println("You will gain knowledge");
}
else
    System.out.println("So you're just going to stand there?");
But what if the road splits not into two, but into ten? You have roads that are "completely to the right", "slightly to the left of that", "a little bit more to the left" and so on, totaling 10 possible roads? Imagine how your "if-then-else" code will grow in this version!
if (option1)
{}
else if (option2)
{}else if (optionN) ...
Suppose you have a 10-way fork in the road (it's important here that the number of options is finite). For such situations, Java has the switch statement.
switch (ExpressionForMakingAChoice) {
           case (Value1):
               Code1;
               break;
           case (Value2):
               Code2;
               break;
...
           case (ValueN):
               CodeN;
               break;
           default:
               CodeForDefaultChoice;
               break;
       }
This is how the statement works:
  • ExpressionForMakingAChoice is evaluated. Then the switch statement compares the resulting value with the next ValueX (in the order in which they are listed).
  • If ExpressionForMakingAChoice matches ValueX, then the code following the colon is executed.
  • If a break statement is encountered, then control is transferred outside the switch statement.
  • If ExpressionForMakingAChoice doesn't match any ValueX, then control passes to CodeForDefaultCase.
Important points
  • In the switch statement, the type of ExpressionForMakingAChoice must be one of the following:

    • byte, short, char, int.
    • Byte, Short, Character, Integer (wrappers of the primitive datatypes).
    • String.
    • Enum.
  • The default block is optional. If it is absent and ExpressionForMakingAChoice doesn't match any ValueX, then no action will be executed.
  • The break statement is not required. If it is absent, the code will continue to be executed (ignoring further comparisons in the case statements) until the first occurrence of break or until the end of the switch statement.
  • If the same code needs to be executed for several choices, we can eliminate duplication by specifying several consecutive case statements.

Let's now take a look at how the switch statement is used in Java

Don't you worry: we're done with the theory. After you see the following examples, everything will become much clearer. Well, let's get started. Let's look at an example from astronomy involving the planets of our solar system. In accordance with the latest international attitudes, we've excluded Pluto (due to the properties of its orbit). We recall that our planets are arranged by their distance from the Sun as follows: Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus and Neptune. Let's write a Java method that takes a planet's ordinal number (relative to its distance from the Sun) and returns the main components of the planet's atmosphere as a List<String>. You'll recall that some planets have a similar atmospheric composition. Thus, Venus and Mars contain mainly carbon dioxide; the atmosphere of Jupiter and Saturn consists of hydrogen and helium; and Uranus and Neptune add methane to the last pair of gases. Here's our function:
public static List<String> getPlanetAtmosphere(int seqNumberFromSun) {
    List<String> result = new ArrayList<>();
    switch (seqNumberFromSun) {
        case 1: result.add("No atmosphere");
            break;
        case 2:
        case 4: result.add("Carbon dioxide");
            break;
        case 3: result.add("Carbon dioxide");
            result.add("Nitrogen");
            result.add ("Oxygen");
            break;
        case 5:
        case 6: result.add("Hydrogen");
            result.add("Helium");
            break;
        case 7:
        case 8: result.add("Methane");
            result.add("Hydrogen");
            result.add("Helium");
            break;
        default:
            break;
    }
    return result;
}
Note that we're using the same code for planets with identical atmospheric compositions. We did this by using consecutive case statements. If we want to get the composition of the atmosphere of our home planet, we call our method with 3 as the argument:
getPlanetAtmosphere(3).
System.out.println(getPlanetAtmosphere(3)) returns ["Carbon dioxide", "Nitrogen", "Oxygen"].
Experiment with break: What happens if we remove all the break statements? Let's give it a try:
public static List<String> getPlanetAtmosphere(int seqNumberFromSun) {
    List<String> result = new ArrayList<>();
    switch (seqNumberFromSun) {
        case 1: result.add("No atmosphere");
        case 2:
        case 4: result.add("Carbon dioxide");
        case 3: result.add("Carbon dioxide");
            result.add("Nitrogen");
            result.add ("Oxygen");
        case 5:
        case 6: result.add("Hydrogen");
            result.add("Helium");
        case 7:
        case 8: result.add("Methane");
            result.add("Hydrogen");
            result.add("Helium");
        default:
    }
    return result;
}
If we print the result of System.out.println(getPlanetAtmosphere(3)), then we find that our home planet isn't so liveable. Or is it? Judge for yourself: ["Carbon dioxide", "Nitrogen", "Oxygen", "Hydrogen", "Helium", "Methane", "Hydrogen", "Helium"]. Why did this happen? The program executes all the case statements after the first match until the end of the switch block.

Excessive optimization of break statements

Note that we can improve the method by arranging the break statements and cases differently.
public static List<String> getPlanetAtmosphere(int seqNumberFromSun) {
    List<String> result = new ArrayList<>();
    switch (seqNumberFromSun) {
        case 1: result.add("No atmosphere");
                break;
        case 3: result.add("Nitrogen");
                result.add ("Oxygen");
        case 2:
        case 4: result.add("Carbon dioxide");
                break;
        case 7:
        case 8: result.add("Methane");
        case 5:
        case 6: result.add("Hydrogen");
                result.add("Helium");
    }
     return result;
}
Looks like less code, right? We've reduced the total number of statements by playing around with the order of the case statements and regrouping them. Now each type of gas is added to the list in only one line of code. The code given in the last example is only to show how things work. We do not recommended writing code this way. If the author of such Java code (let alone other programmers) must maintain it, he or she will find it very difficult to reconstruct the logic behind the formation of those case blocks and the code executed in the switch statement.

Differences from if

Given the outward similarities of if and switch statements, don't forget that the switch statement selects one of the cases based on a SPECIFIC VALUE, whereas the if statement can have any boolean expression. Keep this in mind when designing your code.

Conclusion

  • Use the case statement for more than two branches in order to not clutter your code with if statements.
  • Don't forget to complete the logical block of the branch for each specific value (case statement) by inserting a break statement.
  • The switch statement's expression can be an Enum or String, as well as some primitive types.
  • Remember the default block. Use it to handle unexpected values.
  • To optimize performance, move the code branches corresponding to the most common values to the beginning of the switch block.
  • Don't get carried away in your "optimization" by deleting the break statements at the end of the case statements – such code is difficult to understand, and, as a result, difficult to maintain.