public static void main(String[] args) throws IOException {
List<String> lst = new ArrayList<>();
lst.add("Alpha");
lst.add("Beta");
lst.add("Gamma");
String joinResult = lst.stream().peek(x->System.out.println(x)).map(x -> {
System.out.println("In the middle");return x+"3";}).peek(System.out::println).collect(Collectors.joining());
System.out.println(joinResult);
}
Instead of collect(Collectors.joining() I had count() at the end. But nothing was printed. So is count() no terminal operation ?? count is returning a long so it should be. Really weird.
And the code above prints every element from the beginning to the end. Alpha -> in the middle ->Alpha3 Beta -> in the middle -> Beta3 and so on
Is that how streams work. Every element is processed from beginning to end ?
Your stream source (an ArrayList
) has a known size, the intermediate operations (map()
and peek()
) do not influence that size and therefore count()
can determine the result without calling the intermediate operations.
This is explicitly mentioned in the documentation of Stream.count()
:
API Note:
An implementation may choose to not execute the stream pipeline (either sequentially or in parallel) if it is capable of computing the count directly from the stream source. In such cases no source elements will be traversed and no intermediate operations will be evaluated. Behavioral parameters with side-effects, which are strongly discouraged except for harmless cases such as debugging, may be affected. For example, consider the following stream:
List<String> l = Arrays.asList("A", "B", "C", "D"); long count = l.stream().peek(System.out::println).count();
The number of elements covered by the stream source, a
List
, is known and the intermediate operation, peek, does not inject into or remove elements from the stream (as may be the case for flatMap or filter operations). Thus the count is the size of theList
and there is no need to execute the pipeline and, as a side-effect, print out the list elements.
If you want to make sure that the intermediate operations are executed you can add a dummy filter operation:
long result = lst.stream()
.peek(System.out::println)
.map(x -> { System.out.println("In the middle"); return x+"3"; })
.peek(System.out::println)
.filter(x -> true)
.count();
System.out.println(result);
How are streams processed?
Its the terminal operation that triggers processing of the stream.
A collect()
operation asks the preceeding step (peek()
) for an element. The peek()
step in turn asks its preceeding step (map()
) for an element. The map()
step asks its preceeding step (the first peek()
) for an element. The first peek()
step asks the source (the List
) for an element. This element is then passed back through the various step until it arrives at the collect()
operation. This is repeated until the source cannot provide additional elements.
The count()
operation is a little bit different. While constructing the stream pipeline each step passes some information about itself to the next step. The important property in this case is Spliterator.SIZED
which means that the stream has a finite size that, in the absence of structural source modification, represents an exact count of the number of elements that would be encountered by a complete traversal. The count()
operation checks the preceeding step and if the preceeding step represents a stream with that characteristic it just asks it for the size.
Intermediate operations like map()
and peek()
don't change the number of elements passing through them and therefore pass the Spliterator.SIZED
property to the next stage.
Intermediate operations like flatMap()
and filter()
may pass a different number of elements to the next stages and therefore remove the Spliterator.SIZED
property from the constructed stream.