Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Learn Processing Elements with the forEach() Method | Terminal Operations in the Stream API
Stream API
course content

Course Content

Stream API

Stream API

1. Fundamentals and Functional Capabilities of Stream API
4. Practical Applications of Stream API

book
Processing Elements with the forEach() Method

You're already familiar with the forEach() method since you've used it in practice to print each element of a collection to the console. Let's take a closer look at it and explore its implementations.

It provides a convenient way to process data in a functional style. However, it's important to note that the processing order depends on the stream type and the method used.

There are two implementations of the forEach() method in the Stream API. Let’s go over them one by one.

forEach Method

Executes an action on each element, but the processing order is not guaranteed in parallel streams.

The forEach() method takes a Consumer<T> as an argument—a functional interface that defines an operation for each element in the stream. It's commonly used for logging, printing, or performing side effects.

Practical Example

In an online store, users need to receive notifications about personalized discounts. Since the order doesn’t matter, forEach() is used with a parallel stream for faster execution.

java

Main

copy
1234567891011121314151617181920212223
package com.example; import java.util.List; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<String> customers = List.of( "alice@example.com", "bob@example.com", "charlie@example.com" ); // Parallel stream for faster processing Stream<String> customerStream = customers.parallelStream(); customerStream.forEach(email -> sendDiscountEmail(email)); } private static void sendDiscountEmail(String email) { System.out.println("Sending discount email to: " + email); // Simulating email sending process } }

This code simulates sending personalized discount emails to customers. Since parallelStream() is used, emails are sent in no particular order for improved speed. The forEach() method applies the given action to each email.

forEachOrdered Method

Executes an action while preserving the order of elements, even in parallel streams.

Like forEach, this method also accepts a Consumer<T>, but it ensures that elements are processed in the same order as they appear in the original stream. This is useful when maintaining sequence is crucial.

Practical Example

Imagine a payment system where each transaction must be processed in the exact order it arrives. If payments are handled out of order, it could lead to errors, such as incorrect balance calculations.

java

Main

copy
123456789101112131415161718192021
package com.example; import java.util.List; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<String> payments = List.of( "Payment #5001 - $100", "Payment #5002 - $250", "Payment #5003 - $75" ); Stream<String> paymentStream = payments.parallelStream(); paymentStream.forEachOrdered(payment -> processPayment(payment)); } private static void processPayment(String payment) { System.out.println("Processing payment: " + payment); } }

This code simulates a payment processing system where transactions must be handled in the order they arrive. The use of parallelStream() boosts performance, but forEachOrdered() ensures the sequence remains intact.

Performance Comparison: forEach() vs. forEachOrdered()

Let's measure how much faster forEach() executes compared to forEachOrdered() when working with parallel streams. To do this, you'll create a list of 10 million elements, process them using both methods by computing the square root of each number, and record the execution time.

java

Main

copy
12345678910111213141516171819202122232425262728293031
package com.example; import java.util.List; import java.util.stream.IntStream; import java.util.ArrayList; public class Main { public static void main(String[] args) { List<Integer> numbers = new ArrayList<>(); IntStream.range(0, 10_000_000).forEach(numbers::add); // Create a list with 10 million elements // Measure execution time of `forEach()` long startTime = System.nanoTime(); numbers.parallelStream().forEach(num -> process(num)); long forEachTime = System.nanoTime() - startTime; // Measure execution time of `forEachOrdered()` startTime = System.nanoTime(); numbers.parallelStream().forEachOrdered(num -> process(num)); long forEachOrderedTime = System.nanoTime() - startTime; // Print results System.out.println("forEach execution time: " + forEachTime / 1_000_000 + " ms"); System.out.println("forEachOrdered execution time: " + forEachOrderedTime / 1_000_000 + " ms"); } private static void process(int num) { // Simulate workload Math.sqrt(num); } }

The forEach() method processes elements without preserving order, allowing the stream to freely distribute tasks across available processor cores. This maximizes performance, as each thread can pick elements in any order and process them in parallel without restrictions.

The forEachOrdered() method preserves the original order of elements, which requires additional synchronization. In a parallel stream, elements are first split into chunks for processing, but their results must then be reassembled in the correct order before being passed to the processing method.

1. Which functional interface does the forEach method accept?

2. What output can this code produce?

question mark

Which functional interface does the forEach method accept?

Select the correct answer

question mark

What output can this code produce?

Select the correct answer

Everything was clear?

How can we improve it?

Thanks for your feedback!

Section 3. Chapter 4
We're sorry to hear that something went wrong. What happened?
some-alt