Java Flow Control: for and for-each Loops

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 ifs and fors 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:

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:

  1. Set the local variable i to be 0
  2. Check whether i is less than arr.length, if it is proceed within the block
    2.1. Print out arr[i]
    2.2. Increment i by 1, go to step 2.
  3. If i isn't less than arr.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.

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.