Java Keywords (Part IX): Switch Statements

We are up to 26 grayed out keywords! Considering that the two keywords with asterisks are not going to be covered, that's 54% keywords covered. With those keywords, you can write simple classes that can do all sorts of tasks. You can change the flow of operations by using flow-control keywords like if/else and loop operations using for, while, and do/while. You can also skip iterations while looping or terminate loops early by using continue and break respectively.

This blog will illustrate a more effective way (in certain cases) to change flow of operations by using the keyword switch.

I suggest that if you have not read any of the articles in Java Keyword series, go back read them before proceeding further. Also, go back and read the one about Data Types. All of these articles are from September 2018. That should help you find them quickly. You can also use the "search" option at the top of this page.

Java keyword list

abstract continue for new switch
assert default goto* package synchronized
boolean do if private this
break double implements protected throw
byte else import public throws
case enum instanceof return transient
catch extends int short try
char final interface static void
class finally long strictfp volatile
const* float native super while
Keyword marked with an asterisk (*) are keywords that, although valid, are not used by programmers.

A different way to write nested If/Else statements

In a previous blog, you learned how to create nested "if/else" statements. To review, let's look at the following example.


String color = "";
...
if (color.equals("green") {
  // Keep going
} else if (color.equals("yellow") {
  // Proceed carefully. Maybe reduce speed
} else if (color.equals("red") {
  // Stop!!!
} else {
  // Not sure what to do.
}
The reason why nested if/else structures can become ineffective is because the same variable needs to be reevaluated over and over until a match is found. In the example above, the variable "color" can be evaluated up to a maximum of three times before a decision of which block to execute is made.

A switch statement offers one advantage over nested if/else structures and that is that the variable to be evaluated is evaluated only once. The code below illustrated the previously shown nested if/else but using a switch statement.


String color = "";
...
switch (color) {
  case "green": // if (color.equals("green")
    // Keep going
    break;
  case "yellow": //else if (color.equals("yellow")
    // Proceed carefully. Maybe reduce speed
    break;
  case "red": // else if (color.equals("red")
    // Stop!!!
    break;
  default: //else
    // Not sure what to do.
    break;
}
If the color, for instance, is "yellow", the switch statement will evaluate the value of the color variable and go straight to the case "yellow" line. There are a couple of things worth mentioning before proceeding further. Notice that at the end of each "case" the keyword break is used. In a way, the usage of the keyword is no different that what when used in a loop. The purpose is to "break out" or terminate the switch early. Also, the keyword "default" will be discussed further in another article on a later date. This is because, since Java 8, this keyword is overloaded in Java (meaning that can be used for other purposes).

Converting a nested if/else structure to a switch statement is rather simple as you can see from the previous example. The "if" and "else/if" blocks are converted into a "case" statement and the "else" is converted to the "default" statement. Also, "default" statements are optional just like an "else" clause. However, it is considered bad practice to do so. That said, this conversion could get trickier in certain cases that we will explore later. For now, we need to understand the rules for using "switch" statements.


switch (expression) {
  case value1:
    statement(s);
    break;
  case value2:
    statement(s);
    break;
  …
  case valueN:
    statement(s);
    break;
  default:
    statement(s);
    break;
}

Rules for using switch statements

  1. The switch-expression must yield a value of char, byte, short, or int or String.
  2. Only a single parameter in the switch-expression is allowed.
  3. The switch-expression must ALWAYS be enclosed in parentheses.
  4. All values must be the same data type as the switch-expression and must be literal values (constants).
  5. The only valid evaluation of the switch expression is an equality.
  6. The order of cases is irrelevant, but it is considered good practice for the "default" statement to be the last case in the switch body.
  7. The keyword break is optional, but it should be used at the end of each case in order to terminate the remainder of the switch statement.
By now you should have noticed three underlined sentences in this article. I am about to contradict (not really) these underlined sentences. Let start with the first underlined sentence. Default statements should always be present. At the very least, use it to log unexpected instances when the value evaluated by the "switch" is different than all of the implemented cases. It is possible that in doing this, you discover a new case that was not previously accounted for by your initial requirements. It could also be used to generate an error message (or exception, which we will discuss in a different Java keyword article).

The order of the cases is relevant in some cases and "break" is always required. Consider the following example:


char letterGrade = '\u0000'; // \u0000 is the unicode NULL character
int gradePoint = 0;
...
if (gradePoint >= 90) {
  gradeLetter = 'A';
} else if (gradePoint >= 80) {
  gradeLetter = 'B';
} else if (gradePoint >= 70) {
  gradeLetter = 'C';
} else if (gradePoint >= 60) {
  gradeLetter = 'D';
} else {
  gradeLetter = 'F';
}
Each "if" is evaluating an inequality (gradePoint >= some value). So, how could this nested "if/else" be converted into a "switch"? By providing a distinct case for each possible value AND having ONE of these cases execute the logic. Sounds more confusing? Just look at the solution below.

char letterGrade = '\u0000'; // \u0000 is the unicode NULL character
int gradePoint = 0;
...
switch (gradePoint) {
  case 100: // do nothing and go to next case
  case 99: // do nothing and go to next case
  case 98: // do nothing and go to next case
  case 97: // do nothing and go to next case
  case 96: // do nothing and go to next case
  case 95: // do nothing and go to next case
  case 94: // do nothing and go to next case
  case 93: // do nothing and go to next case
  case 92: // do nothing and go to next case
  case 91: // do nothing and go to next case
  case 90:
    gradeLetter = 'A'; // grade points 100-90 get an 'A'
    break; // finish
  case 89:
  case 88:
  case 87:
  case 86:
  case 85:
  case 84:
  case 83:
  case 82:
  case 81:
  case 80:
    gradeLetter = 'B';
    break;
  case 79:
  case 78:
  case 77:
  case 76:
  case 75:
  case 74:
  case 73:
  case 72:
  case 71:
  case 70:
    gradeLetter = 'C';
    break;
  case 69:
  case 68:
  case 67:
  case 66:
  case 65:
  case 64:
  case 63:
  case 62:
  case 61:
  case 60:
    gradeLetter = 'D';
    break;
  default:
    gradeLetter = 'F';
    break;
}
Although I admit this example is not the best use of a "switch" statement, it is a good one to use to illustrate the conversion of nested "if/else" which evaluates inequalities. The inequality "gradePoint >= 90" encompass all of the integer values between 100 and 90 (inclusive). Therefore, I included a case of each one of these values which are resolved by one of the cases in the group. To make the solution readable, I arranged the cases in descending order. However, the order within this group is not important. That said, the case containing the logic to be executed, MUST be the last one in the group, AND it MUST contain the "break".

By taking advantage of the rule that "break" is an optional instruction, we are able to group cases that need to execute the same logic. In this case, for example, all grade point values between 90 and 100 equate to a letter grade of 'A'. So, for instance, if the value of the gradePoint variable is 95, the "switch-expression" will go directly to the case which handles that value and fall through the case that contains the logic to be executed. So, instead of thinking strictly that "the keyword break is optional", it might be better to remember that the keyword "break" is most likely required at the very least in the cases where logic is executed. This is not to say that it has to be this way every single time. This is where creativity dictate that you deviate from the norm. But these cases are very rare.

Lastly, we determined that the order of the statements can be relevant. For this example to work, we cannot intertwine cases that will be evaluated differently. For example, a case with a value between 80 and 89 cannot be placed between cases with values between 90 and 100. However, in the original example with the traffic light colors, the order is indeed irrelevant. The takeaway here to conclude this topic is that it will all depend in the specific structure you need to create. But, following the examples and rules outlined here should get you close (if not straight to) the solution you will need for your specific case.

Next up, Part X: Try, Catch, and Finally Blocks

Comments

Popular posts from this blog

Implementing Interfaces with Java Records

Customizing Java Records

Exception Handling: File CRUD Operations Example