3

I have a question about the following two codes.

Without parallelism:

Stream.of(1, 2, 3)
    .peek(System.out::println)
    .skip(1)
    .map(n-> n * 10)
    .forEach(System.out::println);

The output is:

1
2
20
3
30

With parallelism:

Stream.of(1, 2, 3)
    .parallel()
    .peek(System.out::println)
    .skip(1)
    .map(n-> n * 10)
    .forEach(System.out::println);

Output is:

2
3
20
30

Why does the output of the parallel stream not include 1?

Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46
  • I wonder which is really correct. You wouldn't expect a peek/limit sequence to peek past the limit. Why should it peek before the skip? Well, sure, because it can, but I'm asking philosophically. – shmosel Jul 15 '22 at 01:33
  • A bit interesting that 1 show up when I change to skip(2). – samabcde Jul 15 '22 at 01:43

1 Answers1

3

There's nothing wrong with skip(), you've asked to skip 1 element, and it always does so. The stream created by Stream.of() is ordered and skip() gives a guarantee that encounter order would always be taken into account.

is constrained to skip not just any n elements, but the first n elements in the encounter order.

Because we don't see 10 in the output, that's the proof that skip() does its job correctly - element 1 never reaches the terminal operation.

There's no output from peek() in parallel (1 is not printed).

This method operates via side effects and according to the API note "exists mainly to support debugging". Contrary to forEach()/forEachOrdered() it's an intermediate operation which is not meant to perform the resulting action. API documentation warns that it could be elided, and you shouldn't rely on it.

The eliding of side-effects may also be surprising. With the exception of terminal operations forEach and forEachOrdered, side-effects of behavioral parameters may not always be executed when the stream implementation can optimize away the execution of behavioral parameters without affecting the result of the computation.

Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46
  • *without affecting the result of the computation* is a crucial qualifier. `peek()` is meant to produce side-effects; I don't see anything in the spec that would permit it to be elided. – shmosel Jul 15 '22 at 01:43
  • @shmosel `peek()` doesn't contribute to the result of stream execution, it never effects the result produced by the terminal operation. If we throw away `peek`, then the action `forEach` does, or the result `collect`, `reduce`, etc. produces would not be effected. – Alexander Ivanchenko Jul 15 '22 at 01:47
  • I guess that can be inferred from the singling-out of `forEach` and `forEachOrdered`, but I would have expected a more explicit disclaimer on `peek` considering it's the only intermediate operation intended for side-effects. – shmosel Jul 15 '22 at 01:49
  • 3
    @shmosel that’s precisely what has been discussed in [“is peek really only for debugging?”](https://stackoverflow.com/a/33636377/2711488). And [the most recent documentation](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/stream/Stream.html#peek(java.util.function.Consumer)) is very clear about it: “*In cases where the stream implementation is able to optimize away the production of some or all the elements (such as with short-circuiting operations like `findFirst`, or in the example described in `count()`), the action will not be invoked for those elements.*” – Holger Jul 15 '22 at 16:04
  • @Holger That's a useful addition, but it still sounds pretty well-defined. If it's meant to cover such a stark inconsistency, I would expect it to be specified in stronger terms. – shmosel Jul 15 '22 at 20:59
  • 2
    @shmosel I can’t imagine, how to express it stronger that a method exists “*to support debugging, where you want to see the elements as they flow past a certain point in a pipeline*” than to say it literally. The method’s purpose is precisely to detect whether and when an element is processed and hence, expected not to report an element if an element is not processed. Like with `IntStream.range(0, 200) .parallel().peek(i -> { if(i > 99) System.out.println(i); }).filter(i -> i > 99) .findFirst().ifPresent(i -> System.out.println("final result: " + i));` the processed elements are unpredictable. – Holger Jul 18 '22 at 10:02