1. Overview

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

In this tutorial, 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 Java 8 concepts, our collection of articles can help.

2. Basics of forEach

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

void forEach(Consumer<? super T> action)

Simply put, the Javadoc of forEach states 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, consider 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:

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 to create an action via a consumer and use forEach API.

Let’s see the three most popular ways we 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 the example, we’ll see that the useful part is actually the code inside the accept() method.

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

3.2. 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.

Since Consumer Interface is a functional interface, we can express it in Lambda:

(argument) -> { //body }

Therefore, our printConsumer is simplified:

name -> System.out.println(name)

And we can pass it to forEach:

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. 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 Collectionlist, set, queue etc. — has the same syntax for using forEach.

Therefore, as we have seen, we can iterate elements of a list this way:

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

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

And a set is similar:

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

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

Finally, let’s look at a Queue that 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.

Java 8 introduces a BiConsumer instead of Consumer in Map‘s forEach so that an action can be performed on both the key and value of a Map simultaneously.

Let’s create a Map with these 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 to iterate over the entries of the Map:

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

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 them is that they are different iterators. The enhanced for-loop is an external iterator, whereas the new forEach method is internal.

5.1. Internal Iterator — forEach

This type of iterator manages 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 iterators mix what and 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);
}

Alhough we are not explicitly invoking hasNext() or next() methods while iterating over the list, the underlying code that 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.