1. Overview
In this short tutorial, We'll see the most common exception "java.lang.IllegalStateException: stream has already been operated upon or closed". We may encounter this exception when working with the Stream interface in Java 8.Can we collect stream twice after closing?
Java Stream reuse – traverse stream multiple times?
Exception
java.lang.IllegalStateException: stream has already been operated upon or closed
2. Look At Problem
2.1 From Java 8 API:
A stream should be operated on (invoking an intermediate or terminal stream operation) only once.2.2 Explanation:
Taking a deeper look at what happens after stream creation.
A) Stream has intermediate operations, short-circuiting terminal operation and terminal operations.
B) Once a Stream is created, A pipe lines associated to this stream will be created.
C) On the stream, We can perform operations like filter(), map(), findAny(), findFirst(), count(), sum() methods on it.
D) Once we perform any operation on the stream, then that operation is placed into the current stream.
E) All operations will be executed and closes stream.
filter(), map() methods are intermediate operations.
findAny(), findFirst() are short-circuiting terminal operation
count() and sum() are terminal operations.
2.3 Example 1:
Next, think if we create a substreams from main stream as below.String conent = "line 1 \n line 2 \\n line 3 \\n line 4 \\n line 5";
Here a stream is created with pipeline.
StreammainStream = Stream.of(conent.split("\n"));
Now, calling filter method on mainStream. Now filter operation is placed in the current pipeline.
mainStream.filter(line -> line.indexOf("line") > -1);
Now again calling filter method on mainStream. That will try to put the filter operation in the pipe line. But the pipe line is currently in operated mode with above filter. So, it it not possible to create another pipe line on the same stream.
mainStream.filter(line -> line.indexOf("1") > -1);
So it will throw now Runtime exception.
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed at java.base/java.util.stream.AbstractPipeline.(AbstractPipeline.java:203) at java.base/java.util.stream.ReferencePipeline. (ReferencePipeline.java:94) at java.base/java.util.stream.ReferencePipeline$StatelessOp. (ReferencePipeline.java:696) at java.base/java.util.stream.ReferencePipeline$2. (ReferencePipeline.java:165) at java.base/java.util.stream.ReferencePipeline.filter(ReferencePipeline.java:164) at com.java.w3schools.blog.exceptions.streams.StreamIntermediateException.main(StreamIntermediateException.java:17)
2.4 Example 2:
Another example program to produce the same exception.// Creating Stream from String. String conent = "line 1 \n line 2 \\n line 3 \\n line 4 \\n line 5"; StreammainStream = Stream.of(conent.split("\n")); // Operation on mainStream Optional firstValue = mainStream.findFirst(); System.out.println("firstValue : " + firstValue.get()); // Operation on mainStream Optional anyValue = mainStream.findAny(); System.out.println("anyValue : " + anyValue.get());
See in this example, calling two methods on mainStream.
First when calling findFirst() then it returns value. But next invoking findAny(), at this time stream or pipe line is already closed. So it can not perform operation on the same stream.
Output:
firstValue : line 1 Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229) at java.base/java.util.stream.ReferencePipeline.findAny(ReferencePipeline.java:548) at com.java.w3schools.blog.exceptions.streams.StreamCloseException.main(StreamCloseException.java:25)
Here firstValue is printed but while getting value from findAny() operation it has failed to get the stream because stream is already closed.
Note:
A Stream should be operated on (invoking an intermediate or terminal stream operation) only once. A Stream implementation may throw IllegalStateException if it detects that the Stream is being reused.
3. Solutions
We have many scenarios where the same stream is required to perform many operations. See the above example 2 where two values are needed.To solve this issue, We need to create two-stream instances for these two separate operations.
Note:
Instead of creating two stream objects separately, We can use built in API Functional Interface Supplier<T> which is super handy in usage.
Supplier> supplier = () -> Stream.of(conent.split("\n")); // findFirst Operation Optional firstValue = supplier.get().findFirst(); System.out.println("firstValue : " + firstValue.get()); // findAny Operation Optional anyValue =supplier.get().findAny(); System.out.println("anyValue : " + anyValue.get());
In this program, created a Supplier object with type of Stream<String>. Supplier has a abstract method that is get() which returns Stream<String>. Now, We have to use Lambda Expression for Supplier Functional Interface with () zero arguments.
So, Whenever we call supplier.get() method than a new fresh Stream<String> object will be created and can perform any stream operation on it.
Output:
Output is returned properly without any errors.
firstValue : line 1 anyValue : line 5
4. Conclusion
In this article, We've seen how to fix "java.lang.IllegalStateException: stream has already been operated upon or closed" problem when working with Java 8 Stream objects.And also seen how to use terminal operations on the same stream using Supplier interface which avoids famous exception "stream has already been operated upon or closed".
All the examples shown in this article are available over GitHub with complete examples.
No comments:
Post a Comment
Please do not add any spam links in the comments section.