Why do I have to chain Stream operations in Java? [duplicate]









up vote
23
down vote

favorite
2













This question already has an answer here:



  • When is a Java 8 Stream considered to be consumed?

    2 answers



I think all of the resources I have studied one way or another emphasize that a stream can be consumed only once, and the consumption is done by so-called terminal operations (which is very clear to me).



Just out of curiosity I tried this:



import java.util.stream.IntStream;

class App
public static void main(String args)
IntStream is = IntStream.of(1, 2, 3, 4);
is.map(i -> i + 1);
int sum = is.sum();




which ends up throwing a Runtime Exception:



Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
at java.util.stream.IntPipeline.reduce(IntPipeline.java:456)
at java.util.stream.IntPipeline.sum(IntPipeline.java:414)
at App.main(scratch.java:10)


This is usual, I am missing something, but still want to ask: As far as I know map is an intermediate (and lazy) operation and does nothing on the Stream by itself. Only when the terminal operation sum (which is an eager operation) is called, the Stream gets consumed and operated on.



But why do I have to chain them?



What is the difference between



is.map(i -> i + 1);
is.sum();


and



is.map(i -> i + 1).sum();


?










share|improve this question















marked as duplicate by Oleksandr, DaveyDaveDave, Federico Peralta Schaffner java
Users with the  java badge can single-handedly close java questions as duplicates and reopen them as needed.

StackExchange.ready(function()
if (StackExchange.options.isMobile) return;

$('.dupe-hammer-message-hover:not(.hover-bound)').each(function()
var $hover = $(this).addClass('hover-bound'),
$msg = $hover.siblings('.dupe-hammer-message');

$hover.hover(
function()
$hover.showInfoMessage('',
messageElement: $msg.clone().show(),
transient: false,
position: my: 'bottom left', at: 'top center', offsetTop: -7 ,
dismissable: false,
relativeToBody: true
);
,
function()
StackExchange.helpers.removeMessages();

);
);
);
Nov 12 at 14:30


This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.










  • 2




    A stream should be 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. However, since some stream operations may return their receiver rather than a new stream object, it may not be possible to detect reuse in all cases.
    – nullpointer
    Nov 12 at 2:16






  • 1




    all streams are driven by a spliterator, and each spliterator is only useful for a single bulk computation
    – 4dc0
    Nov 12 at 2:56















up vote
23
down vote

favorite
2













This question already has an answer here:



  • When is a Java 8 Stream considered to be consumed?

    2 answers



I think all of the resources I have studied one way or another emphasize that a stream can be consumed only once, and the consumption is done by so-called terminal operations (which is very clear to me).



Just out of curiosity I tried this:



import java.util.stream.IntStream;

class App
public static void main(String args)
IntStream is = IntStream.of(1, 2, 3, 4);
is.map(i -> i + 1);
int sum = is.sum();




which ends up throwing a Runtime Exception:



Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
at java.util.stream.IntPipeline.reduce(IntPipeline.java:456)
at java.util.stream.IntPipeline.sum(IntPipeline.java:414)
at App.main(scratch.java:10)


This is usual, I am missing something, but still want to ask: As far as I know map is an intermediate (and lazy) operation and does nothing on the Stream by itself. Only when the terminal operation sum (which is an eager operation) is called, the Stream gets consumed and operated on.



But why do I have to chain them?



What is the difference between



is.map(i -> i + 1);
is.sum();


and



is.map(i -> i + 1).sum();


?










share|improve this question















marked as duplicate by Oleksandr, DaveyDaveDave, Federico Peralta Schaffner java
Users with the  java badge can single-handedly close java questions as duplicates and reopen them as needed.

StackExchange.ready(function()
if (StackExchange.options.isMobile) return;

$('.dupe-hammer-message-hover:not(.hover-bound)').each(function()
var $hover = $(this).addClass('hover-bound'),
$msg = $hover.siblings('.dupe-hammer-message');

$hover.hover(
function()
$hover.showInfoMessage('',
messageElement: $msg.clone().show(),
transient: false,
position: my: 'bottom left', at: 'top center', offsetTop: -7 ,
dismissable: false,
relativeToBody: true
);
,
function()
StackExchange.helpers.removeMessages();

);
);
);
Nov 12 at 14:30


This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.










  • 2




    A stream should be 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. However, since some stream operations may return their receiver rather than a new stream object, it may not be possible to detect reuse in all cases.
    – nullpointer
    Nov 12 at 2:16






  • 1




    all streams are driven by a spliterator, and each spliterator is only useful for a single bulk computation
    – 4dc0
    Nov 12 at 2:56













up vote
23
down vote

favorite
2









up vote
23
down vote

favorite
2






2






This question already has an answer here:



  • When is a Java 8 Stream considered to be consumed?

    2 answers



I think all of the resources I have studied one way or another emphasize that a stream can be consumed only once, and the consumption is done by so-called terminal operations (which is very clear to me).



Just out of curiosity I tried this:



import java.util.stream.IntStream;

class App
public static void main(String args)
IntStream is = IntStream.of(1, 2, 3, 4);
is.map(i -> i + 1);
int sum = is.sum();




which ends up throwing a Runtime Exception:



Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
at java.util.stream.IntPipeline.reduce(IntPipeline.java:456)
at java.util.stream.IntPipeline.sum(IntPipeline.java:414)
at App.main(scratch.java:10)


This is usual, I am missing something, but still want to ask: As far as I know map is an intermediate (and lazy) operation and does nothing on the Stream by itself. Only when the terminal operation sum (which is an eager operation) is called, the Stream gets consumed and operated on.



But why do I have to chain them?



What is the difference between



is.map(i -> i + 1);
is.sum();


and



is.map(i -> i + 1).sum();


?










share|improve this question
















This question already has an answer here:



  • When is a Java 8 Stream considered to be consumed?

    2 answers



I think all of the resources I have studied one way or another emphasize that a stream can be consumed only once, and the consumption is done by so-called terminal operations (which is very clear to me).



Just out of curiosity I tried this:



import java.util.stream.IntStream;

class App
public static void main(String args)
IntStream is = IntStream.of(1, 2, 3, 4);
is.map(i -> i + 1);
int sum = is.sum();




which ends up throwing a Runtime Exception:



Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
at java.util.stream.IntPipeline.reduce(IntPipeline.java:456)
at java.util.stream.IntPipeline.sum(IntPipeline.java:414)
at App.main(scratch.java:10)


This is usual, I am missing something, but still want to ask: As far as I know map is an intermediate (and lazy) operation and does nothing on the Stream by itself. Only when the terminal operation sum (which is an eager operation) is called, the Stream gets consumed and operated on.



But why do I have to chain them?



What is the difference between



is.map(i -> i + 1);
is.sum();


and



is.map(i -> i + 1).sum();


?





This question already has an answer here:



  • When is a Java 8 Stream considered to be consumed?

    2 answers







java java-8 java-stream






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 12 at 2:32









nullpointer

39.1k1074149




39.1k1074149










asked Nov 12 at 2:04









Koray Tugay

8,63926111219




8,63926111219




marked as duplicate by Oleksandr, DaveyDaveDave, Federico Peralta Schaffner java
Users with the  java badge can single-handedly close java questions as duplicates and reopen them as needed.

StackExchange.ready(function()
if (StackExchange.options.isMobile) return;

$('.dupe-hammer-message-hover:not(.hover-bound)').each(function()
var $hover = $(this).addClass('hover-bound'),
$msg = $hover.siblings('.dupe-hammer-message');

$hover.hover(
function()
$hover.showInfoMessage('',
messageElement: $msg.clone().show(),
transient: false,
position: my: 'bottom left', at: 'top center', offsetTop: -7 ,
dismissable: false,
relativeToBody: true
);
,
function()
StackExchange.helpers.removeMessages();

);
);
);
Nov 12 at 14:30


This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.






marked as duplicate by Oleksandr, DaveyDaveDave, Federico Peralta Schaffner java
Users with the  java badge can single-handedly close java questions as duplicates and reopen them as needed.

StackExchange.ready(function()
if (StackExchange.options.isMobile) return;

$('.dupe-hammer-message-hover:not(.hover-bound)').each(function()
var $hover = $(this).addClass('hover-bound'),
$msg = $hover.siblings('.dupe-hammer-message');

$hover.hover(
function()
$hover.showInfoMessage('',
messageElement: $msg.clone().show(),
transient: false,
position: my: 'bottom left', at: 'top center', offsetTop: -7 ,
dismissable: false,
relativeToBody: true
);
,
function()
StackExchange.helpers.removeMessages();

);
);
);
Nov 12 at 14:30


This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.









  • 2




    A stream should be 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. However, since some stream operations may return their receiver rather than a new stream object, it may not be possible to detect reuse in all cases.
    – nullpointer
    Nov 12 at 2:16






  • 1




    all streams are driven by a spliterator, and each spliterator is only useful for a single bulk computation
    – 4dc0
    Nov 12 at 2:56













  • 2




    A stream should be 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. However, since some stream operations may return their receiver rather than a new stream object, it may not be possible to detect reuse in all cases.
    – nullpointer
    Nov 12 at 2:16






  • 1




    all streams are driven by a spliterator, and each spliterator is only useful for a single bulk computation
    – 4dc0
    Nov 12 at 2:56








2




2




A stream should be 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. However, since some stream operations may return their receiver rather than a new stream object, it may not be possible to detect reuse in all cases.
– nullpointer
Nov 12 at 2:16




A stream should be 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. However, since some stream operations may return their receiver rather than a new stream object, it may not be possible to detect reuse in all cases.
– nullpointer
Nov 12 at 2:16




1




1




all streams are driven by a spliterator, and each spliterator is only useful for a single bulk computation
– 4dc0
Nov 12 at 2:56





all streams are driven by a spliterator, and each spliterator is only useful for a single bulk computation
– 4dc0
Nov 12 at 2:56













3 Answers
3






active

oldest

votes

















up vote
45
down vote



accepted










When you do this:



int sum = IntStream.of(1, 2, 3, 4).map(i -> i + 1).sum();


Every chained method is being invoked on the return value of the previous method in the chain.



So map is invoked on what IntStream.of(1, 2, 3, 4) returns and sum on what map(i -> i + 1) returns.



You don't have to chain stream methods, but it's more readable and less error-prone than using this equivalent code:



IntStream is = IntStream.of(1, 2, 3, 4);
is = is.map(i -> i + 1);
int sum = is.sum();


Which is not the same as the code you've shown in your question:



IntStream is = IntStream.of(1, 2, 3, 4);
is.map(i -> i + 1);
int sum = is.sum();


As you see, you're disregarding the reference returned by map. This is the cause of the error.




EDIT (as per the comments, thanks to @IanKemp for pointing this out): Actually, this is the external cause of the error. If you stop to think about it, map must be doing something internally to the stream itself, otherwise, how would then the terminal operation trigger the transformation passed to map on each element? I agree in that intermediate operations are lazy, i.e. when invoked, they do nothing to the elements of the stream. But internally, they must configure some state into the stream pipeline itself, so that they can be applied later.



Despite I'm not aware of the full details, what happens is that, conceptually, map is doing at least 2 things:



  1. It's creating and returning a new stream that holds the function passed as an argument somewhere, so that it can be applied to elements later, when the terminal operation is invoked.


  2. It is also setting a flag to the old stream instance, i.e. the one which it has been called on, indicating that this stream instance no longer represents a valid state for the pipeline. This is because the new, updated state which holds the function passed to map is now encapsulated by the instance it has returned. (I believe that this decision might have been taken by the jdk team to make errors appear as early as possible, i.e. by throwing an early exception instead of letting the pipeline go on with an invalid/old state that doesn't hold the function to be applied, thus letting the terminal operation return unexpected results).


Later on, when a terminal operation is invoked on this instance flagged as invalid, you're getting that IllegalStateException. The two items above configure the deep, internal cause of the error.




Another way to see all this is to make sure that a Stream instance is operated only once, by means of either an intermediate or a terminal operation. Here you are violating this requirement, because you are calling map and sum on the same instance.



In fact, javadocs for Stream state it clearly:




A stream should be 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. However, since some stream operations may return their receiver rather than a new stream object, it may not be possible to detect reuse in all cases.







share|improve this answer


















  • 1




    @Koray To add to that, adding a debug point at both your lines is.map(i -> i + 1); int sum = is.sum();, you can notice the change in the IntPipeline is which is what you are not reusing and I believe since is is already marked as linkedOrConsumed in your case, you see an ISE.
    – nullpointer
    Nov 12 at 2:27






  • 1




    @nullpointer I'm not sure. Memorization sounds too similar to memoization, which is a completely different thing... Maybe tracking is a better term?
    – Federico Peralta Schaffner
    Nov 12 at 2:35






  • 6




    This feels very similar to the age old str.toLowerCase(); vs s = str.toLowerCase(); stumbling block in Java
    – DeadChex
    Nov 12 at 6:48







  • 1




    "As you see, you're disregarding the reference returned by map. This is the cause of the error." - that doesn't make sense to me. As the asker has noted, map() is an intermediate operation, which means that applying it and then throwing away the result via is.map(i -> i + 1); should have no effect on the underlying stream, and thus calling the terminal operation int sum = is.sum(); on that underlying stream should still succeed.
    – Ian Kemp
    Nov 12 at 9:24






  • 1




    I agree with @IanKemp that this answer doesn't really answer the question. As the intermediate operations return a new stream, one would think that the old stream stays untouched. The interface suggests immutability.
    – xehpuk
    Nov 12 at 13:42

















up vote
15
down vote













Imagine the IntStream is a wrapper around your data stream with an
immutable list of operations. These operations are not executed until you need the final result (sum in your case).
Since the list is immutable, you need a new instance of IntStream with a list that contains the previous items plus the new one, which is what '. map' returns.



This means that if you don't chain, you will operate on the old instance, which does not have that operation.



The stream library also keeps some internal tracking of what's going on, that's why it's able to throw the exception in the sum step.



If you don't want to chain, you can use a variable for each step:



IntStream is = IntStream.of(1, 2, 3, 4);
IntStream is2 = is.map(i -> i + 1);
int sum = is2.sum();





share|improve this answer





























    up vote
    3
    down vote














    Intermediate operations return a new stream. They are always lazy; executing an intermediate operation such as filter() does not actually perform any filtering, but instead creates a new stream that, when traversed, contains the elements of the initial stream that match the given predicate.




    Taken from https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html under "Stream Operations and Pipelines"




    At the lowest level, all streams are driven by a spliterator.




    Taken from the same link under "Low-level stream construction"




    Traversal and splitting exhaust elements; each Spliterator is useful for only a single bulk computation.




    Taken from https://docs.oracle.com/javase/8/docs/api/java/util/Spliterator.html






    share|improve this answer


















    • 1




      Yes ... but that doesn't explain what happens if you ignore the result.
      – Stephen C
      Nov 12 at 2:28










    • you were right, i updated my answer
      – 4dc0
      Nov 12 at 2:44

















    3 Answers
    3






    active

    oldest

    votes








    3 Answers
    3






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    45
    down vote



    accepted










    When you do this:



    int sum = IntStream.of(1, 2, 3, 4).map(i -> i + 1).sum();


    Every chained method is being invoked on the return value of the previous method in the chain.



    So map is invoked on what IntStream.of(1, 2, 3, 4) returns and sum on what map(i -> i + 1) returns.



    You don't have to chain stream methods, but it's more readable and less error-prone than using this equivalent code:



    IntStream is = IntStream.of(1, 2, 3, 4);
    is = is.map(i -> i + 1);
    int sum = is.sum();


    Which is not the same as the code you've shown in your question:



    IntStream is = IntStream.of(1, 2, 3, 4);
    is.map(i -> i + 1);
    int sum = is.sum();


    As you see, you're disregarding the reference returned by map. This is the cause of the error.




    EDIT (as per the comments, thanks to @IanKemp for pointing this out): Actually, this is the external cause of the error. If you stop to think about it, map must be doing something internally to the stream itself, otherwise, how would then the terminal operation trigger the transformation passed to map on each element? I agree in that intermediate operations are lazy, i.e. when invoked, they do nothing to the elements of the stream. But internally, they must configure some state into the stream pipeline itself, so that they can be applied later.



    Despite I'm not aware of the full details, what happens is that, conceptually, map is doing at least 2 things:



    1. It's creating and returning a new stream that holds the function passed as an argument somewhere, so that it can be applied to elements later, when the terminal operation is invoked.


    2. It is also setting a flag to the old stream instance, i.e. the one which it has been called on, indicating that this stream instance no longer represents a valid state for the pipeline. This is because the new, updated state which holds the function passed to map is now encapsulated by the instance it has returned. (I believe that this decision might have been taken by the jdk team to make errors appear as early as possible, i.e. by throwing an early exception instead of letting the pipeline go on with an invalid/old state that doesn't hold the function to be applied, thus letting the terminal operation return unexpected results).


    Later on, when a terminal operation is invoked on this instance flagged as invalid, you're getting that IllegalStateException. The two items above configure the deep, internal cause of the error.




    Another way to see all this is to make sure that a Stream instance is operated only once, by means of either an intermediate or a terminal operation. Here you are violating this requirement, because you are calling map and sum on the same instance.



    In fact, javadocs for Stream state it clearly:




    A stream should be 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. However, since some stream operations may return their receiver rather than a new stream object, it may not be possible to detect reuse in all cases.







    share|improve this answer


















    • 1




      @Koray To add to that, adding a debug point at both your lines is.map(i -> i + 1); int sum = is.sum();, you can notice the change in the IntPipeline is which is what you are not reusing and I believe since is is already marked as linkedOrConsumed in your case, you see an ISE.
      – nullpointer
      Nov 12 at 2:27






    • 1




      @nullpointer I'm not sure. Memorization sounds too similar to memoization, which is a completely different thing... Maybe tracking is a better term?
      – Federico Peralta Schaffner
      Nov 12 at 2:35






    • 6




      This feels very similar to the age old str.toLowerCase(); vs s = str.toLowerCase(); stumbling block in Java
      – DeadChex
      Nov 12 at 6:48







    • 1




      "As you see, you're disregarding the reference returned by map. This is the cause of the error." - that doesn't make sense to me. As the asker has noted, map() is an intermediate operation, which means that applying it and then throwing away the result via is.map(i -> i + 1); should have no effect on the underlying stream, and thus calling the terminal operation int sum = is.sum(); on that underlying stream should still succeed.
      – Ian Kemp
      Nov 12 at 9:24






    • 1




      I agree with @IanKemp that this answer doesn't really answer the question. As the intermediate operations return a new stream, one would think that the old stream stays untouched. The interface suggests immutability.
      – xehpuk
      Nov 12 at 13:42














    up vote
    45
    down vote



    accepted










    When you do this:



    int sum = IntStream.of(1, 2, 3, 4).map(i -> i + 1).sum();


    Every chained method is being invoked on the return value of the previous method in the chain.



    So map is invoked on what IntStream.of(1, 2, 3, 4) returns and sum on what map(i -> i + 1) returns.



    You don't have to chain stream methods, but it's more readable and less error-prone than using this equivalent code:



    IntStream is = IntStream.of(1, 2, 3, 4);
    is = is.map(i -> i + 1);
    int sum = is.sum();


    Which is not the same as the code you've shown in your question:



    IntStream is = IntStream.of(1, 2, 3, 4);
    is.map(i -> i + 1);
    int sum = is.sum();


    As you see, you're disregarding the reference returned by map. This is the cause of the error.




    EDIT (as per the comments, thanks to @IanKemp for pointing this out): Actually, this is the external cause of the error. If you stop to think about it, map must be doing something internally to the stream itself, otherwise, how would then the terminal operation trigger the transformation passed to map on each element? I agree in that intermediate operations are lazy, i.e. when invoked, they do nothing to the elements of the stream. But internally, they must configure some state into the stream pipeline itself, so that they can be applied later.



    Despite I'm not aware of the full details, what happens is that, conceptually, map is doing at least 2 things:



    1. It's creating and returning a new stream that holds the function passed as an argument somewhere, so that it can be applied to elements later, when the terminal operation is invoked.


    2. It is also setting a flag to the old stream instance, i.e. the one which it has been called on, indicating that this stream instance no longer represents a valid state for the pipeline. This is because the new, updated state which holds the function passed to map is now encapsulated by the instance it has returned. (I believe that this decision might have been taken by the jdk team to make errors appear as early as possible, i.e. by throwing an early exception instead of letting the pipeline go on with an invalid/old state that doesn't hold the function to be applied, thus letting the terminal operation return unexpected results).


    Later on, when a terminal operation is invoked on this instance flagged as invalid, you're getting that IllegalStateException. The two items above configure the deep, internal cause of the error.




    Another way to see all this is to make sure that a Stream instance is operated only once, by means of either an intermediate or a terminal operation. Here you are violating this requirement, because you are calling map and sum on the same instance.



    In fact, javadocs for Stream state it clearly:




    A stream should be 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. However, since some stream operations may return their receiver rather than a new stream object, it may not be possible to detect reuse in all cases.







    share|improve this answer


















    • 1




      @Koray To add to that, adding a debug point at both your lines is.map(i -> i + 1); int sum = is.sum();, you can notice the change in the IntPipeline is which is what you are not reusing and I believe since is is already marked as linkedOrConsumed in your case, you see an ISE.
      – nullpointer
      Nov 12 at 2:27






    • 1




      @nullpointer I'm not sure. Memorization sounds too similar to memoization, which is a completely different thing... Maybe tracking is a better term?
      – Federico Peralta Schaffner
      Nov 12 at 2:35






    • 6




      This feels very similar to the age old str.toLowerCase(); vs s = str.toLowerCase(); stumbling block in Java
      – DeadChex
      Nov 12 at 6:48







    • 1




      "As you see, you're disregarding the reference returned by map. This is the cause of the error." - that doesn't make sense to me. As the asker has noted, map() is an intermediate operation, which means that applying it and then throwing away the result via is.map(i -> i + 1); should have no effect on the underlying stream, and thus calling the terminal operation int sum = is.sum(); on that underlying stream should still succeed.
      – Ian Kemp
      Nov 12 at 9:24






    • 1




      I agree with @IanKemp that this answer doesn't really answer the question. As the intermediate operations return a new stream, one would think that the old stream stays untouched. The interface suggests immutability.
      – xehpuk
      Nov 12 at 13:42












    up vote
    45
    down vote



    accepted







    up vote
    45
    down vote



    accepted






    When you do this:



    int sum = IntStream.of(1, 2, 3, 4).map(i -> i + 1).sum();


    Every chained method is being invoked on the return value of the previous method in the chain.



    So map is invoked on what IntStream.of(1, 2, 3, 4) returns and sum on what map(i -> i + 1) returns.



    You don't have to chain stream methods, but it's more readable and less error-prone than using this equivalent code:



    IntStream is = IntStream.of(1, 2, 3, 4);
    is = is.map(i -> i + 1);
    int sum = is.sum();


    Which is not the same as the code you've shown in your question:



    IntStream is = IntStream.of(1, 2, 3, 4);
    is.map(i -> i + 1);
    int sum = is.sum();


    As you see, you're disregarding the reference returned by map. This is the cause of the error.




    EDIT (as per the comments, thanks to @IanKemp for pointing this out): Actually, this is the external cause of the error. If you stop to think about it, map must be doing something internally to the stream itself, otherwise, how would then the terminal operation trigger the transformation passed to map on each element? I agree in that intermediate operations are lazy, i.e. when invoked, they do nothing to the elements of the stream. But internally, they must configure some state into the stream pipeline itself, so that they can be applied later.



    Despite I'm not aware of the full details, what happens is that, conceptually, map is doing at least 2 things:



    1. It's creating and returning a new stream that holds the function passed as an argument somewhere, so that it can be applied to elements later, when the terminal operation is invoked.


    2. It is also setting a flag to the old stream instance, i.e. the one which it has been called on, indicating that this stream instance no longer represents a valid state for the pipeline. This is because the new, updated state which holds the function passed to map is now encapsulated by the instance it has returned. (I believe that this decision might have been taken by the jdk team to make errors appear as early as possible, i.e. by throwing an early exception instead of letting the pipeline go on with an invalid/old state that doesn't hold the function to be applied, thus letting the terminal operation return unexpected results).


    Later on, when a terminal operation is invoked on this instance flagged as invalid, you're getting that IllegalStateException. The two items above configure the deep, internal cause of the error.




    Another way to see all this is to make sure that a Stream instance is operated only once, by means of either an intermediate or a terminal operation. Here you are violating this requirement, because you are calling map and sum on the same instance.



    In fact, javadocs for Stream state it clearly:




    A stream should be 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. However, since some stream operations may return their receiver rather than a new stream object, it may not be possible to detect reuse in all cases.







    share|improve this answer














    When you do this:



    int sum = IntStream.of(1, 2, 3, 4).map(i -> i + 1).sum();


    Every chained method is being invoked on the return value of the previous method in the chain.



    So map is invoked on what IntStream.of(1, 2, 3, 4) returns and sum on what map(i -> i + 1) returns.



    You don't have to chain stream methods, but it's more readable and less error-prone than using this equivalent code:



    IntStream is = IntStream.of(1, 2, 3, 4);
    is = is.map(i -> i + 1);
    int sum = is.sum();


    Which is not the same as the code you've shown in your question:



    IntStream is = IntStream.of(1, 2, 3, 4);
    is.map(i -> i + 1);
    int sum = is.sum();


    As you see, you're disregarding the reference returned by map. This is the cause of the error.




    EDIT (as per the comments, thanks to @IanKemp for pointing this out): Actually, this is the external cause of the error. If you stop to think about it, map must be doing something internally to the stream itself, otherwise, how would then the terminal operation trigger the transformation passed to map on each element? I agree in that intermediate operations are lazy, i.e. when invoked, they do nothing to the elements of the stream. But internally, they must configure some state into the stream pipeline itself, so that they can be applied later.



    Despite I'm not aware of the full details, what happens is that, conceptually, map is doing at least 2 things:



    1. It's creating and returning a new stream that holds the function passed as an argument somewhere, so that it can be applied to elements later, when the terminal operation is invoked.


    2. It is also setting a flag to the old stream instance, i.e. the one which it has been called on, indicating that this stream instance no longer represents a valid state for the pipeline. This is because the new, updated state which holds the function passed to map is now encapsulated by the instance it has returned. (I believe that this decision might have been taken by the jdk team to make errors appear as early as possible, i.e. by throwing an early exception instead of letting the pipeline go on with an invalid/old state that doesn't hold the function to be applied, thus letting the terminal operation return unexpected results).


    Later on, when a terminal operation is invoked on this instance flagged as invalid, you're getting that IllegalStateException. The two items above configure the deep, internal cause of the error.




    Another way to see all this is to make sure that a Stream instance is operated only once, by means of either an intermediate or a terminal operation. Here you are violating this requirement, because you are calling map and sum on the same instance.



    In fact, javadocs for Stream state it clearly:




    A stream should be 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. However, since some stream operations may return their receiver rather than a new stream object, it may not be possible to detect reuse in all cases.








    share|improve this answer














    share|improve this answer



    share|improve this answer








    edited Nov 12 at 17:02

























    answered Nov 12 at 2:18









    Federico Peralta Schaffner

    21.8k43369




    21.8k43369







    • 1




      @Koray To add to that, adding a debug point at both your lines is.map(i -> i + 1); int sum = is.sum();, you can notice the change in the IntPipeline is which is what you are not reusing and I believe since is is already marked as linkedOrConsumed in your case, you see an ISE.
      – nullpointer
      Nov 12 at 2:27






    • 1




      @nullpointer I'm not sure. Memorization sounds too similar to memoization, which is a completely different thing... Maybe tracking is a better term?
      – Federico Peralta Schaffner
      Nov 12 at 2:35






    • 6




      This feels very similar to the age old str.toLowerCase(); vs s = str.toLowerCase(); stumbling block in Java
      – DeadChex
      Nov 12 at 6:48







    • 1




      "As you see, you're disregarding the reference returned by map. This is the cause of the error." - that doesn't make sense to me. As the asker has noted, map() is an intermediate operation, which means that applying it and then throwing away the result via is.map(i -> i + 1); should have no effect on the underlying stream, and thus calling the terminal operation int sum = is.sum(); on that underlying stream should still succeed.
      – Ian Kemp
      Nov 12 at 9:24






    • 1




      I agree with @IanKemp that this answer doesn't really answer the question. As the intermediate operations return a new stream, one would think that the old stream stays untouched. The interface suggests immutability.
      – xehpuk
      Nov 12 at 13:42












    • 1




      @Koray To add to that, adding a debug point at both your lines is.map(i -> i + 1); int sum = is.sum();, you can notice the change in the IntPipeline is which is what you are not reusing and I believe since is is already marked as linkedOrConsumed in your case, you see an ISE.
      – nullpointer
      Nov 12 at 2:27






    • 1




      @nullpointer I'm not sure. Memorization sounds too similar to memoization, which is a completely different thing... Maybe tracking is a better term?
      – Federico Peralta Schaffner
      Nov 12 at 2:35






    • 6




      This feels very similar to the age old str.toLowerCase(); vs s = str.toLowerCase(); stumbling block in Java
      – DeadChex
      Nov 12 at 6:48







    • 1




      "As you see, you're disregarding the reference returned by map. This is the cause of the error." - that doesn't make sense to me. As the asker has noted, map() is an intermediate operation, which means that applying it and then throwing away the result via is.map(i -> i + 1); should have no effect on the underlying stream, and thus calling the terminal operation int sum = is.sum(); on that underlying stream should still succeed.
      – Ian Kemp
      Nov 12 at 9:24






    • 1




      I agree with @IanKemp that this answer doesn't really answer the question. As the intermediate operations return a new stream, one would think that the old stream stays untouched. The interface suggests immutability.
      – xehpuk
      Nov 12 at 13:42







    1




    1




    @Koray To add to that, adding a debug point at both your lines is.map(i -> i + 1); int sum = is.sum();, you can notice the change in the IntPipeline is which is what you are not reusing and I believe since is is already marked as linkedOrConsumed in your case, you see an ISE.
    – nullpointer
    Nov 12 at 2:27




    @Koray To add to that, adding a debug point at both your lines is.map(i -> i + 1); int sum = is.sum();, you can notice the change in the IntPipeline is which is what you are not reusing and I believe since is is already marked as linkedOrConsumed in your case, you see an ISE.
    – nullpointer
    Nov 12 at 2:27




    1




    1




    @nullpointer I'm not sure. Memorization sounds too similar to memoization, which is a completely different thing... Maybe tracking is a better term?
    – Federico Peralta Schaffner
    Nov 12 at 2:35




    @nullpointer I'm not sure. Memorization sounds too similar to memoization, which is a completely different thing... Maybe tracking is a better term?
    – Federico Peralta Schaffner
    Nov 12 at 2:35




    6




    6




    This feels very similar to the age old str.toLowerCase(); vs s = str.toLowerCase(); stumbling block in Java
    – DeadChex
    Nov 12 at 6:48





    This feels very similar to the age old str.toLowerCase(); vs s = str.toLowerCase(); stumbling block in Java
    – DeadChex
    Nov 12 at 6:48





    1




    1




    "As you see, you're disregarding the reference returned by map. This is the cause of the error." - that doesn't make sense to me. As the asker has noted, map() is an intermediate operation, which means that applying it and then throwing away the result via is.map(i -> i + 1); should have no effect on the underlying stream, and thus calling the terminal operation int sum = is.sum(); on that underlying stream should still succeed.
    – Ian Kemp
    Nov 12 at 9:24




    "As you see, you're disregarding the reference returned by map. This is the cause of the error." - that doesn't make sense to me. As the asker has noted, map() is an intermediate operation, which means that applying it and then throwing away the result via is.map(i -> i + 1); should have no effect on the underlying stream, and thus calling the terminal operation int sum = is.sum(); on that underlying stream should still succeed.
    – Ian Kemp
    Nov 12 at 9:24




    1




    1




    I agree with @IanKemp that this answer doesn't really answer the question. As the intermediate operations return a new stream, one would think that the old stream stays untouched. The interface suggests immutability.
    – xehpuk
    Nov 12 at 13:42




    I agree with @IanKemp that this answer doesn't really answer the question. As the intermediate operations return a new stream, one would think that the old stream stays untouched. The interface suggests immutability.
    – xehpuk
    Nov 12 at 13:42












    up vote
    15
    down vote













    Imagine the IntStream is a wrapper around your data stream with an
    immutable list of operations. These operations are not executed until you need the final result (sum in your case).
    Since the list is immutable, you need a new instance of IntStream with a list that contains the previous items plus the new one, which is what '. map' returns.



    This means that if you don't chain, you will operate on the old instance, which does not have that operation.



    The stream library also keeps some internal tracking of what's going on, that's why it's able to throw the exception in the sum step.



    If you don't want to chain, you can use a variable for each step:



    IntStream is = IntStream.of(1, 2, 3, 4);
    IntStream is2 = is.map(i -> i + 1);
    int sum = is2.sum();





    share|improve this answer


























      up vote
      15
      down vote













      Imagine the IntStream is a wrapper around your data stream with an
      immutable list of operations. These operations are not executed until you need the final result (sum in your case).
      Since the list is immutable, you need a new instance of IntStream with a list that contains the previous items plus the new one, which is what '. map' returns.



      This means that if you don't chain, you will operate on the old instance, which does not have that operation.



      The stream library also keeps some internal tracking of what's going on, that's why it's able to throw the exception in the sum step.



      If you don't want to chain, you can use a variable for each step:



      IntStream is = IntStream.of(1, 2, 3, 4);
      IntStream is2 = is.map(i -> i + 1);
      int sum = is2.sum();





      share|improve this answer
























        up vote
        15
        down vote










        up vote
        15
        down vote









        Imagine the IntStream is a wrapper around your data stream with an
        immutable list of operations. These operations are not executed until you need the final result (sum in your case).
        Since the list is immutable, you need a new instance of IntStream with a list that contains the previous items plus the new one, which is what '. map' returns.



        This means that if you don't chain, you will operate on the old instance, which does not have that operation.



        The stream library also keeps some internal tracking of what's going on, that's why it's able to throw the exception in the sum step.



        If you don't want to chain, you can use a variable for each step:



        IntStream is = IntStream.of(1, 2, 3, 4);
        IntStream is2 = is.map(i -> i + 1);
        int sum = is2.sum();





        share|improve this answer














        Imagine the IntStream is a wrapper around your data stream with an
        immutable list of operations. These operations are not executed until you need the final result (sum in your case).
        Since the list is immutable, you need a new instance of IntStream with a list that contains the previous items plus the new one, which is what '. map' returns.



        This means that if you don't chain, you will operate on the old instance, which does not have that operation.



        The stream library also keeps some internal tracking of what's going on, that's why it's able to throw the exception in the sum step.



        If you don't want to chain, you can use a variable for each step:



        IntStream is = IntStream.of(1, 2, 3, 4);
        IntStream is2 = is.map(i -> i + 1);
        int sum = is2.sum();






        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Nov 12 at 2:27









        nullpointer

        39.1k1074149




        39.1k1074149










        answered Nov 12 at 2:17









        Pascal Ludwig

        6311614




        6311614




















            up vote
            3
            down vote














            Intermediate operations return a new stream. They are always lazy; executing an intermediate operation such as filter() does not actually perform any filtering, but instead creates a new stream that, when traversed, contains the elements of the initial stream that match the given predicate.




            Taken from https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html under "Stream Operations and Pipelines"




            At the lowest level, all streams are driven by a spliterator.




            Taken from the same link under "Low-level stream construction"




            Traversal and splitting exhaust elements; each Spliterator is useful for only a single bulk computation.




            Taken from https://docs.oracle.com/javase/8/docs/api/java/util/Spliterator.html






            share|improve this answer


















            • 1




              Yes ... but that doesn't explain what happens if you ignore the result.
              – Stephen C
              Nov 12 at 2:28










            • you were right, i updated my answer
              – 4dc0
              Nov 12 at 2:44














            up vote
            3
            down vote














            Intermediate operations return a new stream. They are always lazy; executing an intermediate operation such as filter() does not actually perform any filtering, but instead creates a new stream that, when traversed, contains the elements of the initial stream that match the given predicate.




            Taken from https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html under "Stream Operations and Pipelines"




            At the lowest level, all streams are driven by a spliterator.




            Taken from the same link under "Low-level stream construction"




            Traversal and splitting exhaust elements; each Spliterator is useful for only a single bulk computation.




            Taken from https://docs.oracle.com/javase/8/docs/api/java/util/Spliterator.html






            share|improve this answer


















            • 1




              Yes ... but that doesn't explain what happens if you ignore the result.
              – Stephen C
              Nov 12 at 2:28










            • you were right, i updated my answer
              – 4dc0
              Nov 12 at 2:44












            up vote
            3
            down vote










            up vote
            3
            down vote










            Intermediate operations return a new stream. They are always lazy; executing an intermediate operation such as filter() does not actually perform any filtering, but instead creates a new stream that, when traversed, contains the elements of the initial stream that match the given predicate.




            Taken from https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html under "Stream Operations and Pipelines"




            At the lowest level, all streams are driven by a spliterator.




            Taken from the same link under "Low-level stream construction"




            Traversal and splitting exhaust elements; each Spliterator is useful for only a single bulk computation.




            Taken from https://docs.oracle.com/javase/8/docs/api/java/util/Spliterator.html






            share|improve this answer















            Intermediate operations return a new stream. They are always lazy; executing an intermediate operation such as filter() does not actually perform any filtering, but instead creates a new stream that, when traversed, contains the elements of the initial stream that match the given predicate.




            Taken from https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html under "Stream Operations and Pipelines"




            At the lowest level, all streams are driven by a spliterator.




            Taken from the same link under "Low-level stream construction"




            Traversal and splitting exhaust elements; each Spliterator is useful for only a single bulk computation.




            Taken from https://docs.oracle.com/javase/8/docs/api/java/util/Spliterator.html







            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited Nov 12 at 2:43

























            answered Nov 12 at 2:22









            4dc0

            42539




            42539







            • 1




              Yes ... but that doesn't explain what happens if you ignore the result.
              – Stephen C
              Nov 12 at 2:28










            • you were right, i updated my answer
              – 4dc0
              Nov 12 at 2:44












            • 1




              Yes ... but that doesn't explain what happens if you ignore the result.
              – Stephen C
              Nov 12 at 2:28










            • you were right, i updated my answer
              – 4dc0
              Nov 12 at 2:44







            1




            1




            Yes ... but that doesn't explain what happens if you ignore the result.
            – Stephen C
            Nov 12 at 2:28




            Yes ... but that doesn't explain what happens if you ignore the result.
            – Stephen C
            Nov 12 at 2:28












            you were right, i updated my answer
            – 4dc0
            Nov 12 at 2:44




            you were right, i updated my answer
            – 4dc0
            Nov 12 at 2:44



            這個網誌中的熱門文章

            How to read a connectionString WITH PROVIDER in .NET Core?

            In R, how to develop a multiplot heatmap.2 figure showing key labels successfully

            Museum of Modern and Contemporary Art of Trento and Rovereto