Java Streams

Streams brought a functional and declarative style to Java. They convey elements from a source, such as a collection, through a pipeline of computational operations, But different than collections, streams don’t provide storage, return results without modifying the source, can be unbounded, and are consumable, where elements are visited once in each stream. Here we’ll cover how to create streams, common methods, and how sequencing operations affects output.

Example

Stream.of("acoustic guitar", "bass", "cello", "drums")
    .filter(s -> {
        System.out.println("filter: " + s);
        return true;
    })
    .forEach(s -> System.out.println("forEach: " + s));

Output

filter:  acoustic guitar
forEach: acoustic guitar
filter:  bass
forEach: bass
filter:  cello
forEach: cello
filter:  drums
forEach: drums

Each element moves vertically down the pipeline.

Stream pipelines #

A stream pipeline’s composed of a source, such as a Collection, an array, a generator function, or I/O channel; zero or more intermediate operations such as filter() or map(); and a terminal operation such as forEach() or reduce().

Stream sources can be created in a number of ways.

Intermediate operations #

Method Brief Description Stateful Short circuit
filter Returns a stream consisting of the elements of this stream that match the given predicate. N N
map Returns a stream consisting of the results of applying the given function to the elements of this stream. N N
flatMap Returns a stream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element. N N
distinct Returns a stream consisting of the distinct elements (according to Object.equals(Object)) of this stream. Y N
sorted Returns a stream consisting of the elements of this stream, sorted according to natural order or a Comparator. Y N
limit Returns a stream consisting of the elements of this stream, truncated to be no longer than maxSize in length. Y Y

The only stateful intermediate operations are distinct(), sorted(), limit(), and peak(), whereas limit() is the only operation to short circuit.

See full list on stream interface

Terminal Operations #

After the terminal operation is performed the stream pipeline is considered consumed.

With the exception of iterator() and spliterator(), terminal operations are eager, completing their traversal of the data source and processing of the pipeline before returning.

Method Brief Description Short circuit
forEach Performs an action for each element of this stream. N
forEachOrdered Performs an action for each element of this stream, in the encounter order of the stream if the stream has a defined encounter order. N
toArray Returns an array containing the elements of this stream (possible to pass generator). N
reduce Performs a reduction on the elements of this stream, using the provided identity value and an associative accumulation function, and returns the reduced value. N
collect Performs a mutable reduction operation on the elements of this stream (possibly using a Collector). N
min Returns the minimum element of this stream according to the provided Comparator. N
max Returns the maximum element of this stream according to the provided Comparator. N
count Returns the count of elements in this stream. N

See full list on stream interface

Next, let’s look at how sequencing intermediate operations affect output.

Examples

Stream.of("acoustic guitar", "bass", "cello", "drums")
    .map(s -> {
        System.out.println("map: " + s);
        return s.toUpperCase();
    })
    .filter(s -> {
        System.out.println("filter: " + s);
        return s.startsWith("a");
    })
    .forEach(s -> System.out.println("forEach: " + s));

Output

map: ACOUSTIC GUITAR
filter: ACOUSTIC GUITAR
forEach: ACOUSTIC GUITAR
map: BASS
filter: BASS
map: CELLO
filter: CELLO
map: DRUMS
filter: DRUMS

Many extra traversals.

Flip map() and filter().

Stream.of("acoustic guitar", "bass", "cello", "drums")
    .filter(s -> {
        System.out.println("filter: " + s);
        return s.startsWith("a");
    })
    .map(s -> {
        System.out.println("map: " + s);
        return s.toUpperCase();
    })
    .forEach(s -> System.out.println("forEach: " + s));

Output

filter: acoustic guitar
map: acoustic guitar
forEach: acoustic guitar
filter: bass
filter: cello
filter: drums

Let’s randomize the list and the stateful sorted() operation.

Stream.of("drums", "acoustic guitar", "cello", "bass")
    .sorted((s1, s2) -> {
        System.out.printf("sort: %s; %s\n", s1, s2);
        return s1.compareTo(s2);
    })
    .filter(s -> {
        System.out.println("filter: " + s);
        return !s.startsWith("a") 
    })
    .map(s -> {
        System.out.println("map: " + s);
        return s.toUpperCase();
    })
    .forEach(s -> System.out.println("forEach: " + s));

Output

sort: acoustic guitar; drums
sort: cello; acoustic guitar
sort: cello; drums
sort: cello; acoustic guitar
sort: bass; cello;
sort: bass; drums
filter: acoustic guitar
map: acoustic guitar
forEach: acoustic guitar
filter: bass
filter: cello
filter: drums

The sort operation is executed on the entire input collection. In other words sorted is executed horizontally.

Stream operations parameters are always instances of a functional interface such as Function, and are often lambda expressions or method references. Unless specified they must be non-null.

A stream is operated on (invoking an intermediate or terminal stream operation) only once. This rules out, for example, “forked” streams, where the same source feeds two or more pipelines, or multiple traversals of the same stream. A stream implementation may throw IllegalStateException if it detects that the stream is being reused.

Certain stream sources (such as List or arrays) are intrinsically ordered, and some intermediate operations, such as sorted(), may impose an encounter order on an otherwise unordered stream. Further, some terminal operations may ignore encounter order, such as forEach().

[1] Stream interface https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html

 
9
Kudos
 
9
Kudos

Now read this

Type Checking Techniques in JavaScript: Part 1 of 2

One of the early milestones in grocking any programming language is knowing how to use its data types. Should be easy for a small scripting language like JavaScript, right? And yet because of the elusive var keyword, implicit type... Continue →