Cagatay
Cagatay

Reputation: 335

Consuming stream multiple times with Stream.sum()

A stream should be operated on (invoking an intermediate or terminal stream operation) only once.

I get the idea, but how come finding the sum of a stream does not consume it? I can run the code below, without any exceptions.

double totalPrice = stream.mapToDouble(product -> product.price).sum();
List<Product> products = stream.map(this::convert).collect(Collectors.toList());

Why sum is not a terminal operator? How is it different from collecting stream elements into a list?

Upvotes: 5

Views: 3838

Answers (1)

Zabuzard
Zabuzard

Reputation: 25943

Documentation

Whether a stream can be consumed once or multiple times is not specified. The documentation says "should be operated ... only once", not "must" or "can".

It depends on the underlying implementation and source of the stream. As such, the capability to execute it multiple times is undefined for streams in general.


Implementations

You might be able to find a stream that can be consumed twice or multiple times, especially for custom implementations or implementations where a prevention detection would maybe be too expensive to implement.

But again, it is not supported by the documentation. It may be by a current implementation, but this can change at any time, in any Java release. It may throw exceptions, it may result in buggy behavior, it is not specified.

Here is a small example which uses sum() that throws an exception because it has such a detection (JDK 11):

Stream<Integer> stream = List.of(1, 2, 3, 4).stream();

int first = stream
    .mapToInt(i -> i)
    .sum();

int second = stream
    .mapToInt(i -> i)
    .sum();

// throws IllegalStateException: stream has already been operated upon or closed

Conclusion

The intention is definitely to not use a stream multiple times, as hinted by the documentation. So even if it is maybe possible for a certain implementation, avoid it.


Why?

You may ask yourself why Stream does not support multiple iteration. After all, collections like ArrayList have no issue with that and it seems to be a common use case.

Streams have a much bigger scope than collections. A stream created on a typical collection could likely easily support multiple iterations. But a stream tied to a resource like a file or web-connection, for example the stream returned by Files.lines(...) could not. Such a feature would be extremely resource heavy and expensive for files and maybe not even supported for others, like a web-connection which may be closed afterwards.

It goes even further, you can easily create an infinite stream that generates random numbers:

Stream<Double> stream = Stream.generate(Math::random);

While it could easily support multiple usages, it would be impossible to generate the same sequence again when iterated again.

Another example, a stream that consumes resources without restoring them:

Queue<Integer> queue = ...
Stream<Integer> stream = Stream.generate(queue::poll);

This stream will remove elements from the queue. They are gone after stream usage. Another iteration of the stream wont be able to get hands on the dead objects anymore.

Upvotes: 4

Related Questions