Lazy Execution – Streams

Lazy Execution

A stream pipeline does not execute until a terminal operation is invoked. In other words, its intermediate operations do not start processing until their results are needed by the terminal operation. Intermediate operations are thus lazy, in contrast to the terminal operation, which is eager and executes when it is invoked.

An intermediate operation is not performed on all elements of the stream before performing the next operation on all elements resulting from the previous stream. Rather, the intermediate operations are performed back-to-back on each element in the stream. In a sense, the loops necessary to perform each intermediate operation on all elements successively are fused into a single loop (technically called loop fusion). Thus only a single pass is required over the elements of the stream.

Example 16.3 illustrates loop fusion resulting from lazy execution of a stream pipeline at (2). The intermediate operations now include print statements to announce their actions at (3) and (4). Note that we do not advocate this practice for production code. The output shows that the elements are processed one at a time through the pipeline when the terminal operation is executed. A CD is filtered first, and if it is a pop music CD, it is mapped to its title and the terminal operation includes this title in the result list. Otherwise, the CD is discarded. When there are no more CDs in the stream, the terminal operation completes, and the stream is consumed.

Short-circuit Evaluation

The lazy nature of streams allows certain kinds of optimizations to be performed on stream operations. We have already seen an example of such an optimization that results in loop fusion of intermediate operations.

In some cases, it is not necessary to process all elements of the stream in order to produce a result (technically called short-circuit execution). For instance, the limit() intermediate operation creates a stream of a specified size, making it unnecessary to process the rest of the stream once this limit is reached. A typical example of its usage is to turn an infinite stream into a finite stream. Another example is the takeWhile() intermediate operation that short-circuits stream processing once its predicate becomes false.

Certain terminal operations (anyMatch(), allMatch(), noneMatch(), findFirst(), findAny()) are also short-circuit operations, since they do not need to process all elements of the stream in order to produce a result (p. 949).

Stateless and Stateful Operations

An stateless operation is one that can be performed on a stream element without taking into consideration the outcome of any processing done on previous elements or on any elements yet to be processed. In other words, the operation does not retain any state from processing of previous elements in order to process a new element. Rather, the operation can be performed on an element independently of how the other elements are processed.

A stateful operation is one that needs to retain state from previously processed elements in order to process a new element.

The intermediate operations distinct(), dropWhile(), limit(), skip(), sorted(), and takeWhile() are stateful operations. All other intermediate operations are stateless. Examples of stateless intermediate operations include the filter() and map() operations.

Leave a Reply

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