Executing Stream Pipelines

Apart from the fact that an intermediate operation always returns a new stream and a terminal operation never does, another crucial difference between these two kinds of operations is the way in which they are executed. Intermediate operations use lazy execution; that is, they are executed on demand. Terminal operations use eager execution; that is, they are executed immediately when the terminal operation is invoked on the stream. This means the intermediate operation will never be executed unless a terminal operation is invoked on the output stream of the intermediate operation, whereupon the intermediate operations will start to execute and pull elements from the stream created on the data source.

The execution of a stream pipeline is illustrated in Figure 16.1c. The stream() method just returns the initial stream whose data source is cdList. The CD objects in cdList are processed in the stream pipeline in the same order as in the list, designated as cd0, cd1, cd2, cd3, and cd4. The way in which elements are successively processed by the operations in the pipeline is shown horizontally for each element. The execution of the pipeline only starts when the terminal operation toList() is invoked, and proceeds as follows:

  1. cd0 is selected by the filter() operation as it is a pop music CD, and the collect() operation places it in the list created to accumulate the results.
  2. cd1 is discarded by the filter() operation as it is not a pop music CD.
  3. cd2 is selected by the filter() operation as it is a pop music CD, and the collect() operation places it in the list created to accumulate the results.
  4. cd3 is discarded by the filter() operation as it is not a pop music CD.
  5. cd4 is discarded by the filter() operation as it is not a pop music CD.

In Figure 16.1c, when the stream is exhausted, execution of the collect() terminal operation completes and execution of the pipeline stops. Note that there was only one pass over the elements in the stream. From Figure 16.1c, we see that the resulting list contains only cd0 and cd2, which is the result of the query. Printing the resulting popCDs list produces the following output:

Click here to view code image

[<Jaav, “Java Jive”, 8, 2017, POP>, <Funkies, “Lambda Dancing”, 10, 2018, POP>]

A stream is considered consumed once a terminal operation has completed execution. A stream that has been consumed cannot be reused, and any attempt to use it will result in a nasty java.lang.IllegalStateException.

The code presented in this subsection is shown in Example 16.2.

Example 16.2 Data Processing Using Streams

Click here to view code image

import java.util.List;
import java.util.stream.Stream;
public class StreamPipeLine {
  public static void main(String[] args) {
    List<CD> cdList = List.of(CD.cd0, CD.cd1, CD.cd2, CD.cd3, CD.cd4);
    // (A) Query to create a list of all CDs with pop music.
    List<CD> popCDs = cdList.stream()              // (1) Stream creation.
        .filter(CD::isPop)                         // (2) Intermediate operation.
        .toList();                                 // (3) Terminal operation.
    System.out.println(popCDs);
    // (B) Equivalent to (A).
    Stream<CD> stream1 = cdList.stream();          // (1a) Stream creation.
    Stream<CD> stream2 = stream1.filter(CD::isPop);// (2a) Intermediate operation.
    List<CD> popCDs2 = stream2.toList();           // (3a) Terminal operation.
    System.out.println(popCDs2);
  }
}

Output from the program:

Click here to view code image

[<Jaav, “Java Jive”, 8, 2017, POP>, <Funkies, “Lambda Dancing”, 10, 2018, POP>]
[<Jaav, “Java Jive”, 8, 2017, POP>, <Funkies, “Lambda Dancing”, 10, 2018, POP>]

Leave a Reply

Your email address will not be published. Required fields are marked *