Reputation: 3538
My understanding was that a Java 8 Stream
is considered to be consumed once a terminal operation, such as forEach()
or count()
, is performed.
However, the test case multipleFilters_separate
below throws an IllegalStateException
even though filter
is a lazy intermediate operation, just called as two statements. And yet, I can chain the two filter operations into a single statement and it works.
@Test(expected=IllegalStateException.class)
public void multipleFilters_separate() {
Stream<Double> ints = Stream.of(1.1, 2.2, 3.3);
ints.filter(d -> d > 1.3);
ints.filter(d -> d > 2.3).forEach(System.out::println);
}
@Test
public void multipleFilters_piped() {
Stream<Double> ints = Stream.of(1.1, 2.2, 3.3);
ints.filter(d -> d > 1.3)
.filter(d -> d > 2.3)
.forEach(System.out::println);
}
From this, I'm assuming a Stream
is considered to be consumed after the first statement that uses it whether that statement calls a terminal operation or not. Does that sound right?
Upvotes: 20
Views: 5126
Reputation: 393791
A Stream
is considered consumed once a terminal operation is executed. However, even multiple intermediate operations are not supposed to be executed for the same Stream
instance, as stated in the Stream
javadoc:
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.
In case of intermediate stream operations, you should call the next operation on the Stream
returned by the previous operation:
public void multipleFilters_separate() {
Stream<Double> ints = Stream.of(1.1, 2.2, 3.3);
ints = ints.filter(d -> d > 1.3);
ints.filter(d -> d > 2.3).forEach(System.out::println);
}
Upvotes: 22
Reputation: 15684
According to the Stream
Javadoc:
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.
In your case, the call to filter
itself is detecting an attempt to fork your stream into two different streams. Rather than wait and cause problems once the terminal operation is added, it's performing a pre-emptive strike to make it clear from any stack trace you get exactly where your problem is.
Upvotes: 12