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 if
s and for
s 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
anddo-while
statementsfor
andenhanced for
statementsbreak
andcontinue
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;
...
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
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.