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 for Loop
for
loops are typically used when the number of iterations is "fixed" in some way. Either we know exactly how many times the loop will be executed or we have a better idea than "until n becomes m".
For loops are often used with arrays:
for (initialization; terminationCondition; update) {
// Code here...
}
And a simple implementation:
int[] arr = {1,2,3,4,5,6,7,8,9};
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
This is a very simple for
statement, and this is how it executes:
- Set the local variable
i
to be 0 - Check whether
i
is less thanarr.length
, if it is proceed within the block
2.1. Print outarr[i]
2.2. Incrementi
by 1, go to step 2. - If
i
isn't less thanarr.length
, proceed after the block
As soon as step 2 finds that i
is greater than or equal to arr.length
, the loops stops its execution, and Java continues execution from the line after the loop block.
Note: Here, the placement of the increment operator (++
) isn't important. If our update step was ++i
instead, nothing would change as the update step always executes after the code in the for
loop block.
This code, of course, would print out:
1
2
3
4
5
6
7
8
9
There are three different blocks used in the for
loop that allow us to do this: the initialization block, the termination condition, and the update step.
Initialization Block
An initialization block in the for
loop is used to initialize a variable. In our example, we wanted the variable i
to start at 0, since 0 is the first index in an array.
This block is executed only once, upon the start of the for
loop. We can also define multiple variables of the same type here:
// Single variable
for (int i = 0; i < 10; i++) {
// Code
}
// Multiple variables
for (int i = 10, j = 25; i < arr.length; i++) {
// Code
}
// Multiple variables
for (int i = 10, double j = 25.5; i < arr.length; i++) {
// WON'T compile because we used two different types - int and double
}
The variables initialized inside the for
statement can only be used inside the for
block. Accessing them out of their scope will result in an exception, as expected:
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
System.out.println(i);
The variable i
has been referenced out of scope:
MyClass.java:6: error: cannot find symbol
System.out.println(i);
^
symbol: variable i
location: class MyClass
1 error
Note: The code in the initialization block is optional and doesn't have to be included. Though, the block has to be. Therefore, we can write something like this:
int i = 0;
for (; i < 10; i++) {
System.out.println(i);
}
System.out.println("\ni variable is " + i);
And it would result in the same output as if the initialization block were there, except that the i
variable is no longer out of scope after executing the for
loop:
0
1
2
3
4
5
6
7
8
9
i variable is 10
The initialization block is technically there, since we included the ;
end of it, but there's no code inside of it.
Termination Condition
The termination condition tells the for
loop to execute the code as long as it's true
. If the termination condition evaluates to false
, the update step and the rest of the for
loop are skipped. There can only be one termination condition:
for (int i = 0; i < 10; i++) {
System.out.println("i = " + i + " | i < 10 is true");
}
// Compiles as there's only one termination condition,
// although there's two operators - two relational and one logical
for (int i = 6; i < 10 & i > 5; i++) {
System.out.println("i = " + i + " | i < 10 is true");
}
// WON'T compile, multiple separate termination conditions
for (int i = 0; i < 10, i > 5; i++) {
System.out.println("i = " + i + " | i < 10 is true");
}
The output of the first loop:
i = 0 | i < 10 is true
i = 1 | i < 10 is true
i = 2 | i < 10 is true
i = 3 | i < 10 is true
i = 4 | i < 10 is true
i = 5 | i < 10 is true
i = 6 | i < 10 is true
i = 7 | i < 10 is true
i = 8 | i < 10 is true
i = 9 | i < 10 is true
Upon reaching 10
the condition i < 10
is no longer true
and the code stops looping.
And the output of the second loop:
i = 6 | i < 10 is true
i = 7 | i < 10 is true
i = 8 | i < 10 is true
i = 9 | i < 10 is true
Note: The termination condition is also optional. It's valid to define a for
loop without a termination condition. Though, excluding the termination condition will make the code loop infinitely, or until a StackOverflowError
occurs.
for (int i = 0; ; i++) {
System.out.println("Looping forever!");
}
Even though there's no code in the termination condition, you have to include a semicolon to mark that you've decided to leave it empty, otherwise the code won't compile.
MyClass.java:3: error: ';' expected
for (int i = 0; i++) {
^
1 error
Update Step
The update step most often decrements/increments some kind of control variable (in our case - i
) after every iteration of the loop. Essentially, it makes sure that our termination condition is met at some point - in our case incrementing i
until it reaches 10.
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!
The update step will only execute if the termination condition evaluates to true
, and after the code in the for
loop block has been executed. You can have multiple update steps if you'd like, and can even call methods:
for (int i = 0; i < 10; i++) {
// Code
}
int j = 10;
for (int i = 0; i < 10; i++, j--) {
System.out.println(i + " | " + j);
}
// Multiple variables
for (int i = 10; i < arr.length; i++, doSomething()) {
System.out.println("Hello from the for loop");
}
public static void doSomething() {
System.out.println("Hello from another method");
}
And the output of this loop would be:
Hello from the for loop
Hello from another method!
Hello from the for loop
Hello from another method!
Hello from the for loop
Hello from another method!
Hello from the for loop
Hello from another method!
This also confirms that the update step executes after the code inside of the block.
Note: The update step is also optional, just like the initialization block and termination condition. It can be replaced by code inside of the for
loop, and we could increment/decrement the control variable before the code that way too.
Empty for Loop?
Since all three blocks of the for
loop are technically optional, we could, without a problem, write this for
loop:
int i = 0;
// This will compile, all blocks are "present" but no code is actually there
for (;;) {
if (i < 10)
System.out.println(i);
i++;
}
If you take a closer look, this really looks similar to a while loop:
int i = 0;
while (i < arr.length) {
System.out.println(i);
i++;
}
Everything that can be done with a while
loop can be done with a for
loop, and vice versa, and the choice between them is decided by readability and convenience.
for
loops and the three parts provide a clear overview of what the conditions of the loop are, where it begins, how the state is changed, and when it stops iterating. It makes them very concise and easily manipulated.
Given this, it's highly recommended to avoid changing the control variable (i
in our case) outside of the parentheses after for
, unless absolutely necessary.
Nested for Loops
Nested for
loops are also very common, especially when iterating through multi-dimensional arrays (arrays that have other arrays as elements):
int[][] multiArr = {{1,2,3},{4},{5,6}};
for (int i = 0; i < multiArr.length; i++) {
for (int j = 0; j < multiArr[i].length; j++) {
System.out.print(multiArr[i][j] + " ");
}
System.out.println();
}
Running this code would yield:
1 2 3
4
5 6
Enhanced for Loop
The enhanced for
, or for-each
is a special type of for
loop that can be used for any object that implements the Iterable
interface, and arrays.
It is designed to go through elements in order, one after the other, from the beginning to the end. This differs from the traditional for
loop in that we could've made the step whatever we wanted - we could have, for example, printed out every other element:
int[] arr = {1,2,3,4,5,6,7,8,9};
for (int i = 0; i < arr.length; i+=2) {
System.out.println(arr[i]);
}
Notice that we increment i
by 2 every time. This would have printed out the following:
1
3
5
7
9
Instead, with for-each
we iterate through all elements using the following syntax:
for (ElementType localVar : somethingIterable) {
// Code
}
The loop stores one element after the other in the localVar
variable, until there are no more elements left. It was created to avoid making traditional for
loops that went through arrays/collections sequentially. We no longer need to specify a counter, set a starting position and an end position, manually index the array, and we no longer have to worry about boundaries - all of which can be very tedious to write out.
All this has been automatized via the for-each
.
List<String> list = new ArrayList<>(Arrays.asList("a","b","c"));
for (String s : list) {
System.out.println(s);
}
// The traditional for syntax...
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
// ...or...
for (Iterator<String> i = list.iterator(); i.hasNext();) {
System.out.println(i.next());
}
The first loop is the cleanest and requires less maintenance from our side. This piece of code could effectively be read as: "for every String s
in the list of strings list
, do something to s
.
Nested for-each Loops
for-each
also supports multidimensional arrays, one of our previous for
loops printed out elements of a two-dimensional array - this is how it would look using for-each
:
int[][] multiArr = {{1,2,3},{4},{5,6}};
// Keep in mind that x is an array, since multiArr
// is an array of arrays
for (int[] x : multiArr) {
for (int y : x) {
System.out.print(y + " ");
}
System.out.println();
}
Similarly, we can use it to iterate nested collections:
ArrayList<String> stronglyTyped = new ArrayList<>();
stronglyTyped.add("Java");
stronglyTyped.add("Go");
stronglyTyped.add("Harbour");
stronglyTyped.add("Haskell");
ArrayList<String> weaklyTyped = new ArrayList<>();
weaklyTyped.add("C++");
weaklyTyped.add("C");
weaklyTyped.add("JavaScript");
ArrayList<ArrayList<String>> programmingLanguages = new ArrayList<ArrayList<String>>();
programmingLanguages.add(stronglyTyped);
programmingLanguages.add(weaklyTyped);
The programmingLanguages
arraylist contains two other arraylists - stronglyTyped
and weaklyTyped
.
To traverse them, we would simply write:
for (ArrayList<String> languages : programmingLanguages) {
for (String language : languages) {
System.out.println(language);
}
}
The output:
Java
Go
Harbour
Haskell
C++
C
JavaScript
Modifying Values During for-each
It's important to note that you can change the values of the items you're iterating. For an example, in the previous example, if change the System.out.println(language)
with System.out.println(language.toUppercase())
, we'd be greeted with:
JAVA
GO
HARBOUR
HASKELL
C++
C
JAVASCRIPT
This is because we're dealing with objects. When iterating through them, we're assigning their reference variables to the language
string. Calling any change on the language
reference variable will reflect in the original one too. In the case of strings, it may not actually affect the objects due to the String Pool, but you get the point.
This, however, doesn't happen with primitive variables, as with them, the value is simply copied into the variable we're accessing. So changing it won't affect the original variables.
Bonus: forEach Method
While, not the focus of this article, we should mention that there is a new way to loop over lists since Java 8. The .forEach()
method is now available, which can be coupled with lambda expressions for single-line loops.
In some cases this can be used instead of the for-each
loop, further simplifying iteration:
list.forEach(x -> System.out.println(x));
Conclusion
Flow control in code is essential just about 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.
for
and for-each
loops are good for executing a block of code a known number of times, often with an array or other type of iterable. More complex loops are also possible, like incrementing the index by 2, or by incrementing and checking multiple variables.