Guide to Java Streams: forEach() with Examples

Introduction

The forEach() method is part of the Stream interface and is used to execute a specified operation, defined by a Consumer.

The Consumer interface represents any operation that takes an argument as input, and has no output. This sort of behavior is acceptable because the forEach() method is used to change the program's state via side-effects, not explicit return types.

Therefore, the best target candidates for Consumers are lambda functions and method references. It's worth noting that forEach() can be used on any Collection.

forEach() on List

The forEach() method is a terminal operation, which means that after we call this method, the stream along with all of its integrated transformations will be materialized. That is to say, they'll "gain substance", rather than being streamed.

Let's generate a small list:

List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);

Traditionally, you could write a for-each loop to go through it:

for (Integer element : list) {
    System.out.print(element + " ");
}

This would print:

1 2 3

Alternatively, we can use the forEach() method on a Stream:

list.stream().forEach((k) -> {
    System.out.print(k + " ");
});

This also outputs:

1 2 3

We can make this even simpler via a method reference:

list.stream().forEach(System.out::println);

forEach() on Map

The forEach() method is really useful if we want to avoid chaining many stream methods. Let's generate a map with a few movies and their respective IMDB scores:

Map<String, Double> map = new HashMap<String, Double>();
map.put("Forrest Gump", 8.8);
map.put("The Matrix", 8.7);
map.put("The Hunt", 8.3);
map.put("Monty Python's Life of Brian", 8.1);
map.put("Who's Singin' Over There?", 8.9);

Now, let's print out the values of each film that has a score higher than 8.4:

map.entrySet()
        .stream()
        .filter(entry -> entry.getValue() > 8.4)
        .forEach(entry -> System.out.println(entry.getKey() + ": " + entry.getValue()));

This results in:

Forrest Gump: 8.8
The Matrix: 8.7
Who's Singin' Over There?: 8.9

Here, we've converted a Map to a Set via entrySet(), streamed it, filtered based on the score and finally printed them out via a forEach(). Instead of basing this on the return of filter(), we could've based our logic on side-effects and skipped the filter() method:

map.entrySet()
        .stream()
        .forEach(entry -> {
            if (entry.getValue() > 8.4) {
                System.out.println(entry.getKey() + ": " + entry.getValue());
                }
            }
        );

This results in:

Forrest Gump: 8.8
The Matrix: 8.7
Who's Singin' Over There?: 8.9

Finally, we can omit both the stream() and filter() methods by starting out with forEach() in the beginning:

map.forEach((k, v) -> {
        if (v > 8.4) {
            System.out.println(k + ": " + v);
         }
     });

This results in:

Forrest Gump: 8.8
The Matrix: 8.7
Who's Singin' Over There?: 8.9

forEach() on Set

Let's take a look at how we can use the forEach method on a Set in a bit more tangible context. First, let's define a class that represents an Employee of a company:

public class Employee {
    private String name;
    private double workedHours;
    private double dedicationScore;
    
    // Constructor, Getters and Setters
    
    public void calculateDedication() {
        dedicationScore = workedHours*1.5;
    }
    
    public void receiveReward() {
        System.out.println(String
            .format("%s just got a reward for being a dedicated worker!", name));
    }
}

Imagining we're the manager, we'll want to pick out certain employees that have worked overtime and award them for the hard work. First, let's make a Set:

 Set<Employee> employees = new HashSet<Employee>();

 employees.add(new Employee("Vladimir", 60));
 employees.add(new Employee("John", 25));
 employees.add(new Employee("David", 40));
 employees.add(new Employee("Darinka", 60));

Then, let's calculate each employee's dedication score:

employees.stream().forEach(Employee::calculateDedication);

Now that each employee has a dedication score, let's remove the ones with a score that's too low:

Set<Employee> regular = employees.stream()
        .filter(employee -> employee.getDedicationScore() <= 60)
        .collect(Collectors.toSet());
employees.removeAll(regular);

Finally, let's reward the employees for their hard work:

employees.stream().forEach(employee -> employee.receiveReward());

And for clarity's sake, let's print out the names of the lucky workers:

System.out.println("Awarded employees:");
employees.stream().map(employee -> employee.getName()).forEach(employee -> System.out.println(employee));

After running the code above, we get the following output:

Vladimir just got a reward for being a dedicated worker!
Darinka just got a reward for being a dedicated worker!
Awarded employees:
Vladimir
Darinka

Side-effects Vs Return Values

The point of every command is to evaluate the expression from start to finish. Everything in-between is a side-effect. In this context, it means altering the state, flow or variables without returning any values.

Let's take a look at the difference on another list:

List<Integer> targetList = Arrays.asList(1, -2, 3, -4, 5, 6, -7);

This approach is based on the returned values from an ArrayList:

long result1 = targetList
        .stream()
        .filter(integer -> integer > 0)
        .count();
System.out.println("Result: " + result1);

This results in:

Result: 4

And now, instead of basing the logic of the program on the return type, we'll perform a forEach() on the stream and add the results to an AtomicInteger (streams operate concurrently):

AtomicInteger result2 = new AtomicInteger();

targetList.stream().forEach(integer -> {
            if (integer > 0)
                result2.addAndGet(1);
            });
System.out.println("Result: " + result2);

Conclusion

The forEach() method is a really useful method to use to iterate over collections in Java in a functional approach.

In certain cases, they can massively simplify the code and enhance clarity and brevity. In this article, we've gone over the basics of using a forEach() and then covered examples of the method on a List, Map and Set.

We've covered the difference between the for-each loop and the forEach(), as well as the difference between basing logic on return values versus side-effects.

Author image
Belgrade