0

With the following code I always get 0 as the first output. Is that guaranteed?

    import java.util.Random;
    import java.util.stream.Stream;

    public class Main {

    public static void main(String[] args) {
        Main m = new Main();
        m.rope();
    }


    class Wrapper {
        public int x = 0;
    }


    void rope() {
        Stream.generate( Wrapper::new ).limit(100000)
                .peek( w -> asum += w.x )
                .forEach( w -> mutate( w ) );

        System.out.println( asum );
        System.out.println( bsum );
    }


    void mutate( Wrapper w ) {
        w.x = r.nextInt(2);
        bsum += w.x;
    }


    protected long asum = 0;
    protected long bsum = 0;
    Random r = new Random( 0 );
}

According to 'peek' documents "... performing the provided action on each element as elements are consumed ...". However for non-parallel streams it has to be either before or after.

In other words, when the 'terminal operation' happens for an element is it removed from the pipeline before the terminal operation or after?

(I am pretty sure 'before' is the right answer, but trying to understand some complex stream code where the opposite seems to be assumed.)

Aelian
  • 665
  • 1
  • 8
  • 13
  • 1
    Tangentially, the Stream operations which accept a function all specify that the function should be [non-interfering](https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#NonInterference). This means that, at the interface level, it's safe to leave _when_ the peek happens as undefined. – Andrew Rueckert Aug 04 '17 at 22:00

2 Answers2

1

The function in peek will always be called before the function in forEach for each element.

Joe C
  • 15,324
  • 8
  • 38
  • 50
1

Think about it. If you have a streaming pipeline like

...peek(xxx).map(...).peek(yyy).collect(...)

the xxx code would see the element before the mapping function is applied, and the yyy code would see the element after the mapping function is applied, and before the element is handed off to the final operation, as the element "flows" down the stream.

A particular element will be processed by the steps of the stream in the order of the stream pipeline, but there is generally no constraint on the order of different elements being processed, especially if stream is parallel.

E.g. the stream engine could decide to send all elements to the xxx code, before mapping all the element, and then finally send all elements to the yyy code. The stream pipeline would function correctly if it did. It won't, or course, because that defeats the purpose of streaming, but it would be valid to do so.

Andreas
  • 154,647
  • 11
  • 152
  • 247
  • Thanks. So is it correct to generalize that stream operations are guaranteed to be applied in the sequence they are specified irrespective of the operator name as far as **one single** input element is concerned? Of course this has to be the case when the 'output' of one operation is the input to the next, but I wasn't sure about an operation where the input and the output are the same. – Aelian Aug 05 '17 at 01:28
  • 1
    @Jeevaka Correct. – Andreas Aug 05 '17 at 15:03
  • 1
    @Jeevaka: `peek` would be quite useless, even for its intended purpose, if it could change its position in the processing chain. But that doesn’t change the fact that using it like in your question's code is highly discouraged. – Holger Aug 06 '17 at 15:21
  • @Holger: Agree on `peek` behavior. Can you elaborate a bit what exactly is 'highly discouraged'? I read [this](https://softwareengineering.stackexchange.com/questions/308977/is-it-an-antipattern-to-use-peek-to-modify-a-stream-element) too. – Aelian Aug 07 '17 at 18:29
  • 1
    @Jeevaka See [answer #2](https://stackoverflow.com/a/33636377/5221149) of [In Java streams is peek really only for debugging?](https://stackoverflow.com/q/33635717/5221149). – Andreas Aug 07 '17 at 22:39
  • @Angreas Yes, I read that too. I feel that with "... This method exists **mainly** to support debugging ...", the document leaves some gray area for foolhardy to wonder into. BTW, my original name for the test method 'rope()' was 'enoughRopeToHang()' and I encountered this pattern in a much complex code base that I am maintaining. – Aelian Aug 08 '17 at 15:50
  • 1
    @Jeevaka "Mainly" for debugging leaves it open to similar non-critical uses, such as logging, statistics gathering, progress monitoring, ... All useful features, but not a required part of the system, i.e. if `peek()` is removed, your application will continue to function correctly. – Andreas Aug 08 '17 at 16:05
  • @Andreas Thanks, if that is the case it make perfect sense. Since I am drifting away from the original question, I posted a [new question](https://stackoverflow.com/questions/45636628/in-java-is-there-a-well-defined-set-of-optional-or-implementation-defined-fe). – Aelian Aug 11 '17 at 13:39