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.
- Collections via the stream() and parallelStream() methods
- Arrays via Arrays.stream(Object[])
- Static factory methods on the stream classes, such as Stream.of(Object[]), IntStream.range(int, int) or Stream.iterate(Object, UnaryOperator)
- Numerous other stream-bearing methods such as BitSet.stream(), Pattern.splitAsStream(java.lang.CharSequence), and JarFile.stream().
Intermediate operations #
- Each return a new stream to the pipeline.
- Lazy. Traversal of the pipeline source doesn’t begin until the terminal operation is executed, giving opportunities for optimization. For example, “find the first String with three consecutive vowels” need not examine all the input strings.
- Divided into stateless and stateful operations. In stateless each element can be processed independently of operations on other elements (e.g. filter() and map()) whereas stateful use state from previously seen elements (e.g. distinct(), and sorted()).
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