Complete Guide to Java 8 forEach

Feb 19, 2021 · 18 mins read

In Java, there are several ways to iterate over collections, arrays or maps. When Java first appeared, iteration was achieved using Iterators. In Java 5, the enhanced for loop or for-each (for(String s: collection)) was introduced to eliminate clutter associated with iterators.

Java 8 introduced a new concise and powerful way of iterating over collections: the forEach() method. While this method is the focus of this post, before we dive into it, we’ll explore its cousin, the for-each loop to illustrate differences and similarities that we’ll explore later. Without further ado, let’s get started.

1. for-each aka enhanced for loop

Before we look at the forEach() method, let’s look at its close cousin the for-each loop. Both are used for iterating through constructors. Both are virtually the same, yet have their own identities at the same time. We’ll contrast them later.

But first, let’s review the for-each loop.

The for-each loop was added to Java starting with version 5. It allows developers to iterate over collections, just like iterators did in the previous versions of Java. It is also sometimes known as the enhanced for loop.

Let’s take a look at how to use for-each loop. In the code below, suppose we have a a collection of colors as strings (e.g. List<String> colors). We can iterate over the colors collection using for-each to print each color:

for (String color : colors) {
    System.out.println(color);
}

Tip: The loop above reads as “for each string color in colors.” In other words, when we see : replace it with “in”.

Let’s now contrast the for-each loop above with the previous way of using iterators to iterate over collections. The for-each loop above is equivalent to:

for (Iterator<String> i = colors.iterator(); i.hasNext();){
    System.out.println(i.next());
}

Now, you may start thinking why are there two ways of iterating and when to use one over the other. The short answer is to always prefer and use for-each loop over iterators. For a longer answer and explanation, see below.

1.1 for-each VS for loop

Before for-each loop, developers iterated over collections using the traditional for, while or do-while loops and iterators.

Why provide yet another way to traverse a collection? It turns out there was a good reason. Iterating over collection using traditional loop structures like the for loop not only clutter up the code, it also introduces an opportunity for introducing errors. From Oracle:

Iterating over a collection is uglier than it needs to be. Consider the following method, which takes a collection of timer tasks and cancels them:

void cancelAll(Collection<TimerTask> c) {
    for (Iterator<TimerTask> i = c.iterator(); i.hasNext(); )
        i.next().cancel();
}

The iterator is just clutter. Furthermore, it is an opportunity for error. The iterator variable occurs three times in each loop: that is two chances to get it wrong. The for-each construct gets rid of the clutter and the opportunity for error. Here is how the example looks with the for-each construct:

void cancelAll(Collection<TimerTask> c) {
    for (TimerTask t : c)
        t.cancel();
}

Not having to declare iterators is the key benefit of the for-each loop. By providing an idiomatic approach, it reduces the clutter and opportunity for introducing errors by not having to declare or deal with the iterator variable.

Prefer and use for-each loop over iterators. Unless you need access to the index variable, or you want to remove elements (in which case you’d need the iterator so you can call its remove() method.)

Let’s now shift our focus to the main attraction of this post: the forEach() method.

In the next section, we’ll review the for-each method in detail and contrast it with the for-each loop.

2 Using the forEach() Method

So far, we have examined the for-each loop. Let’s now shift our focus to the forEach() method.

The Iterable interface (which is the parent of the Collection interface) got a new method called forEach() in Java 8. It provides another, more functional way of iterating over collections.

Let’s look at its method definition first:

void forEach(Consumer<? super T> action)

According to Javadocs:

“Performs the given action for each element of the Iterable until all elements have been processed or the action throws an exception.”

This is best understood with examples so let’s take a look at a few examples.

2.1 Passing Consumer to forEach() method

In traditional for loops using iterators and even the for-each loop, we loop through the collection and perform the action manually within the loop. forEach() method changes this by providing a more concise way to achieve the same result but do it a little differently, more aligned to functional programming paradigm.

If you look at the method definition of the forEach() method from the last section, it takes one argument called action. This argument specifies the action to be performed on items within the collection. The action must implement the Consumer interface which is a functional interface:

  1. Functional interfaces contain a single abstract method.
  2. Functional interface don’t return a result and cannot be used as assignment target for Lambda expressions.

This is the Consumer interface:

@FunctionalInterface
public interface Consumer {
    void accept(T t);
}

Let’s apply this to our previous example of iterating over colors in a collection (List<String> colors.)

First, we’ll instantiate the the Consumer interface to define the action that we want to perform on the items in the collection:

Consumer<String> printColorsAction = new Consumer<String>() {
    public void accept(String color) {
        System.out.println(color);
    };
};

We can now pass printColorsAction to the forEach method:

colors.forEach(printColorsAction)

That’s all. When we execute the code above, it will print the items (colors) in our collection. The result is the same as using for (String color: colors) or the for-each loop. Both prints a list of all the color strings in the colors collection. We’ll look at differences between for-each and forEach() method later, but they achieve the same result for all intents and purposes.

Although the example above works, Lambda expressions are much better for expressing the same logic and should be preferred over instantiating and passing consumer objects or actions. (It’s still good to know about the Consumer interface so you can understand what goes under the hood in Lambda expressions.)

2.2 Using Lambda Expressions

Functional interfaces allow us to use Lambda expressions to write concise code by avoiding object instantiation or anonymous classes (which look even more ugly especially when there’s quite a bit of code.)

Recall from the last example (in 2.1) that forEach() method takes a Consumer object which is a Functional Interface. Armed with with information, we can re-write the code from the last example:

colors.forEach(color -> System.out.println(color));

That’s all we had to do. The code above uses Lambda expressions and is much more readable and concise than the code in the previous section which achieves the same result.

3. Using for-each loop forEach() method with Collections

3.1 Iterating over Collections

We can iterate over arrays using both the forEach loop and the forEach() method. Let’s look at one example. In this example, we’ll iterate over a collection (List<String>) using both forEach() method and the for-each loop.

3.1.1 Iterate using forEach() method

 List<String> colors = Arrays.asList("red", "green", "blue");

colors.forEach(color -> System.out.println(color))

// or use method reference
colors.forEach(System.out::println);

3.1.2 Iterate using for-each loop

In contrast, here’s how to iterate using for-each loop.

 List<String> colors = Arrays.asList("red", "green", "blue");
        
for (String color : colors) {
    System.out.println(color);
}

3.2 Iterating over Maps

The Map interface provides a variant of forEach() method that accepts a functional interface called BiConsumer. This interface is a specialization of the Consumer interface we looked at in the last section. Unlike Consumer, it takes 2 arguments, hence the name BiConsumer. Maps accept BiConsumer so that the action can be performed on both the key and value simultaneously.

Map<Integer, String> colorsMap = new HashMap<>();
colorsMap.put(1, "red");
colorsMap.put(2, "green");
colorsMap.put(3, "blue");

colorsMap.forEach((key, value) -> System.out.println(key + " => " + value));

Prints:

1 => red
2 => green
3 => blue

We can also iterate over the map using for-each loop and EntrySet:

Map<Integer, String> colorsMap = new HashMap<>();
colorsMap.put(1, "red");
colorsMap.put(2, "green");
colorsMap.put(3, "blue");

for (Map.Entry entrySet : colorsMap.entrySet() ) {
    System.out.println(entrySet.getKey() + " => " + entrySet.getValue());
}

4. For-each loop vs forEach() method

So far, as we have been exploring both the for-each loop (for (String color: colors) and forEach() method (colors.forEach(...)) in this post. Both approaches achieve the same net result: they allow developers to iterate over collections. But is there a point in using one over the other? Or, why use forEach() method over for-each loop?

While I’m not sure I can answer this question exhaustively. I’ll try to highlight some differences and let you decide which approach you want to use. I have seen developers using both so it’s good to be familiar with how each works.

4.1 forEach() method

The forEach() method was introduced to allow writing code using the functional style of programming. If you’re going to iterate and just call a single method, forEach() method is also more readable (someList.forEach (obj::someMethod))

One of the main benefits of the forEach() method is that you could do some fancy things pretty easily like executing the code in parallel just by adding parallelStream().

colors.parallelStream().forEach(System.out::println);

Achieving the same without forEach() method will require you to write your own multithreading code.

The forEach() method is an internal iterator. In internal iterators, the code that generates values decides when to invoke the code that uses that value. It is the iterator that controls the iteration. In other words, you tell the collection “take this code and apply it on all elements of the collection.”

4.2 for-each loop

In contrast, the for-each loop is an external iterator. Here, the client code controls the iteration such as defining how to iterate and what to do with the values in each iteration. The client code remains in full control such as when to move to the next item and when to stop or break out of the loop.

Personally, I find myself mostly using for-each loop quite often (perhaps, I’m just used to it.) I do use forEach() method for simple things like printing elements in a collection (but I have to do this consciously.)

Here are a couple of things developers can not do with the forEach() method.

4.2.1 forEach() method cannot use non-final variables

If you want to use a variable inside a lambda expression (including forEach() method), it must be final or effectively final. The following code can’t be converted to forEach() without making the prevColor object final.

String prevColor = null; // not final

for(String color : colors) {
    if( prevColor != null )
        mix(prevColor, color);
    prevColor = color;
}

Although in practice, you could wrap the reference into AtomicReference or make it final because why not. final variables rock!

4.2.2 Exception handling requires additional code

Lambda expressions allow developers to write concise, readable code. However, if during the course of its operation, the code can throw exceptions, especially checked exceptions, it requires you to write additional try/catch blocks in Lambda hence resulting in loss of its conciseness.

colors.forEach(c -> {
    try {
        int hex = Integer.parseInt(c)
    } catch (NumberFormatException e) {
        System.err.println(
          "Cannot convert to integer: " + e.getMessage());
    }
});

Extra: In the example above, we are using parseInt method to convert string to integer. For other ways or more information, see this.

There is no equivalent of break statement in forEach() method.

Although, if you need this sort of functionality and don’t want to use the for-each loop, you could use the streams methods such as findFirst() or anyMatch(). But there is no break equivalent if you’re using forEach() method to iterate over collections.

5. Performance of for-each loop vs forEach() method

Some developers assume that the for-each loop, or the tradition C-style for loop is faster than forEach() method, especially when iterating over arrays. In my research, I found that the benchmarks were mixed or the performance difference was negligible.

The performance is highly JVM dependent and could change from one version to another.

Here’s an interesting performance analysis. Source.

5.1 Benchmark Code

@Benchmark
public void timeTraditionalForEach(int reps){
    for (int i = 0; i < reps; i++) {
        for (TestObject t : list) {
            t.result++;
        }
    }
}

@Benchmark
public void timeForEachAnonymousClass(int reps){
    for (int i = 0; i < reps; i++) {
        list.forEach(new Consumer<TestObject>() {
            @Override
            public void accept(TestObject t) {
                t.result++;
            }
        });
    }
}

@Benchmark
public void timeForEachLambda(int reps){
    for (int i = 0; i < reps; i++) {
        list.forEach(t -> t.result++);
    }
}

@Benchmark
public void timeForEachOverArray(int reps){
    for (int i = 0; i < reps; i++) {
        for (TestObject t : array) {
            t.result++;
        }
    }
}

5.2 Results for -client

The following results illustrate the benchmark when the Java HotSpot was run with the -client option.

Performance_Benchmarks_forEach_Java

5.3 Results for -server

The following results illustrate the benchmark when the Java HotSpot was run with the -client option.

Performance_Benchmarks_forEach_Java_Server

These benchmarks are interesting because they reveal an interesting point. When running in -server mode, the performance across all benchmarks is the same. But the forEach() method outperforms the traditional for loop for an array list when running in -client mode!

6. Summary

We covered a lot of ground in this post. Let’s break it down. The main focus of this post was the forEach() method in Java, which allows developers a way to iterate over collections.

First we looked at two other ways of iterating over collections, including:

  • for-each loop (for(Integer n: numbers)
  • Using iterators

We learned that for-each loop should be preferred over iterators as it produces more readable code and is less error prone.

Then we explored the forEach() method with several examples and compared it the for-each loop. While both achieve the same result, there are some differences. for-each loop is an external iterator while forEach() method is internal, and also more functional.

Which approach do you use for iterating over collections? Do you have any tips or use cases where one approach is better than the other? Please leave a comment below.

#java #boost

You May Also Enjoy


If you like this post, please share using the buttons above. It will help CodeAhoy grow and add new content. Thank you!


Speak Your Mind