Guide to the Java 8 forEach

1. Overview

Introduced in Java 8, the forEach loop provides programmers with a new, concise and interesting way for iterating over a collection.

In this article, we’ll see how to use forEach with collections, what kind of argument it takes and how this loop differs from the enhanced for-loop.

If you need to brush up some concepts of Java 8, we have a collection of articles that can help you.

2. Basics of forEach

In Java, the Collection interface has Iterable as its super interface – and starting with Java 8 this interface has a new API:

void forEach(Consumer<? super T> action)

Simply put, the Javadoc of forEach stats that it “performs the given action for each element of the Iterable until all elements have been processed or the action throws an exception.”

And so, with forEach, we can iterate over a collection and perform a given action on each element, like any other Iterator.

For instance, a for-loop version of iterating and printing a Collection of Strings:

for (String name : names) {
    System.out.println(name);
}

We can write this using forEach as:

names.forEach(name -> {
    System.out.println(name);
});

3. Using the forEach Method

We use forEach to iterate over a collection and perform a certain action on each element. The action to be performed is contained in a class that implements the Consumer interface and is passed to forEach as an argument.

The Consumer interface is a functional interface (an interface with a single abstract method). It accepts an input and returns no result.

Here’s the definition:

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

Therefore, any implementation, for instance, a consumer that simply prints a String:

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

can be passed to forEach as an argument:

names.forEach(printConsumer);

But that is not the only way of creating an action via a consumer and using forEach API.

Let’s see the 3 most popular ways in which we will use the forEach method:

3.1. Anonymous Consumer Implementation

We can instantiate an implementation of the Consumer interface using an anonymous class and then apply it as an argument to the forEach method:

Consumer<String> printConsumer= new Consumer<String>() {
    public void accept(String name) {
        System.out.println(name);
    }
};
names.forEach(printConsumer);

This works well but if we analyze at the example above we’ll see that the actual part that is of use is the code inside the accept() method.

Although Lambda expressions are now the norm and easier way to do this, it’s still worth to know how to implement the Consumer interface.

3.2. A Lambda Expression

The major benefit of Java 8 functional interfaces is that we can use Lambda expressions to instantiate them and avoid using bulky anonymous class implementations.

As Consumer Interface is a functional interface, we can express it in Lambda in the form of:

(argument) -> { //body }

Therefore, our printConsumer simplifies to:

name -> System.out.println(name)

And we can pass it to forEach as:

names.forEach(name -> System.out.println(name));

Since the introduction of Lambda expressions in Java 8, this is probably the most common way to use the forEach method.

Lambdas do have a very real learning curve, so if you’re getting started, this write-up goes over some good practices of working the new language feature.

3.3. A Method Reference

We can use method reference syntax instead of the normal Lambda syntax where a method already exists to perform an operation on the class:

names.forEach(System.out::println);

4. Working with forEach


==== 4.1. Iterating over a Collection

Any iterable of type Collection – list, set, queue etc. have the same syntax for using forEach.

Therefore, as we have already seen, to iterate elements of a list:

List<String> names = Arrays.asList("Larry", "Steve", "James");

names.forEach(System.out::println);

Similarly for a set:

Set<String> uniqueNames = new HashSet<>(Arrays.asList("Larry", "Steve", "James"));

uniqueNames.forEach(System.out::println);

Or let’s say for a Queue which is also a Collection:

Queue<String> namesQueue = new ArrayDeque<>(Arrays.asList("Larry", "Steve", "James"));

namesQueue.forEach(System.out::println);

4.2. Iterating over a Map – using Map’s forEach

Maps are not Iterable, but, they do provide their own variant of forEach that accepts a *BiConsumer*. 

BiConsumer was introduced instead of Consumer in Iterable’s forEach so that an action can be performed on both the key and value of a Map simultaneously.

Let’s create a Map having entries:

Map<Integer, String> namesMap = new HashMap<>();
namesMap.put(1, "Larry");
namesMap.put(2, "Steve");
namesMap.put(3, "James");

Next, let’s iterate over namesMap using Map’s forEach:

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

As we can see here, we’ve used a BiConsumer:

(key, value) -> System.out.println(key + " " + value)

to iterate over the entries of the Map.

4.3. Iterating over a Map – by iterating entrySet

We can also iterate the EntrySet of a Map using Iterable’s forEach.

Since the entries of a Map are stored in a Set called EntrySet, we can iterate that using a forEach:

namesMap.entrySet().forEach(entry -> System.out.println(
  entry.getKey() + " " + entry.getValue()));

5. Foreach vs For-Loop

From a simple point of view, both loops provide the same functionality – loop through elements in a collection.

The main difference between the two of them is that they are different iterators – the enhanced for-loop is an external iterator whereas the new forEach method is an internal one.

5.1. Internal Iterator – forEach

This type of iterator manage the iteration in the background and leaves the programmer to just code what is meant to be done with the elements of the collection.

The iterator instead, manages the iteration and makes sure to process the elements one-by-one.

Let’s see an example of an internal iterator:

names.forEach(name -> System.out.println(name));

In the forEach method above, we can see that the argument provided is a lambda expression. This means that the method only needs to know what is to be done and all the work of iterating will be taken care of internally.

5.2. External Iterator – for-loop

[[external-iterator—​for-loop]]External iterators mix the what and the how the loop is to be done.

Enumerations, Iterators and enhanced for-loop are all external iterators (remember the methods iterator(), next() or hasNext() ? ). In all these iterators it’s our job to specify how to perform iterations.

Consider this familiar loop:

for (String name : names) {
    System.out.println(name);
}

Though we are not explicitly invoking hasNext() or next() methods while iterating over the list, the underlying code which makes this iteration work uses these methods. This implies that the complexity of these operations is hidden from the programmer but it still exists.

Contrary to an internal iterator in which the collection does the iteration itself, here we require external code that takes every element out of the collection.

6. Conclusion

In this article, we showed that the forEach loop is more convenient than the normal for-loop.

We also saw how the forEach method works and what kind of implementation can receive as an argument in order to perform an action on each element in the collection.

Finally, all the snippets used in this article are available in our Github repository.

Leave a Reply

Your email address will not be published.