The Master Class of "Learn Spring Security" is live:
1. Overview
Introduced in Java 8, the forEach loop provides programmers 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 than 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 – by just passing a class that implements the Consumer interface.
2.1. The Consumer Interface
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); }
With that in mind, let’s consider the example below:
List<String> names = new ArrayList<>(); names.add("Larry"); names.add("Steve"); names.add("James"); names.add("Conan"); names.add("Ellen"); names.forEach(new Consumer<String>() { public void accept(String name) { System.out.println(name); } });
We can create an anonymous inner class that implements the Consumer interface and then define the action to perform on each element in the collection (in this case just printing out the name).
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.
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.
So let’s consider the following:
Consumer<String> consumerNames = name -> { System.out.println(name); }; names.forEach(consumerNames);
Now we have a Consumer that we can pass as an argument to the forEach method.
Also, we can use the Consumer in other forEach loops in the application.
Lambdas do have a very real learning curve, so if you’re getting started, this writeup goes over some good practices of working the new language feature.
3. 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.
3.1. Internal Iterator
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, rather than managing the iteration and making sure that all the elements are processed 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.
3.2. External Iterator
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 the iteration will be performed.
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 from an internal iterator in which the collection does the iteration itself, here we require external code that takes every element out of the collection.
4. Using the forEach Method
As we saw earlier in the article, in order to use this method we need to pass it as argument an implementation of the interface Consumer.
We already know that since the Consumer is a functional interface we can use a lambda expression to simplify the code. But there is also another kind of argument to use with this method.
Let’s see the 3 most used ways in which you will use the forEach method.
4.1. Anonymous Consumer Implementation
We already saw this implementation. We instantiated an implementation of the Consumer interface using an anonymous class implementation and then apply it as an argument to the forEach method.
Here is the complete example:
Consumer<String> consumerNames = new Consumer<String>() { public void accept(String name) { System.out.println(name); } }; names.forEach(consumerNames);
In the example above, we want to print out each element of the list. In order to this, we instantiate a Consumer (consumerNames) using an anonymous class implementation that just prints strings. The Consumer is then passed as a parameter to the forEach method.
Although lambda expressions are now the normal and easier way to do this, it’s still worth that you know how you can implement the Consumer interface.
4.2. A Lambda Expression
Here, the lambda expression is an anonymous representation of a function descriptor of a functional interface.
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.
4.3. A Method Reference
In a case where a method already exists to perform an operation on the class, this syntax can be used instead of the normal lambda expression syntax:
names.forEach(System.out::println);
5. 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.
You can see the examples used in our Github repository.