Reputation: 15864
I know that whenever we call any terminal method
on a stream
, it gets closed.
If we try to call any other terminal function on a closed stream it will result in a java.lang.IllegalStateException: stream has already been operated upon or closed
.
However, what if we want to reuse the same stream more than once?
How to accomplish this?
Upvotes: 9
Views: 7468
Reputation: 126
A naive example of 3 different terminal-like operations on stream elements at once:
Of course this is not very elegant, but it works:
List<Integer> famousNumbers = List.of(0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55);
Stream<Integer> numbersStream = famousNumbers.stream();
Stream<Integer> numbersGreater5Stream = numbersStream.filter(x -> x > 5);
var ref = new Object() {
int counter = 0;
int sum = 0;
};
numbersGreater5Stream.forEach(x -> {
System.out.print(x + " ");
ref.counter++;
ref.sum += x;
});
System.out.println("\n" + ref.counter + " " + ref.sum);
Upvotes: 0
Reputation: 112
No. You cannot use a stream multiple times. What you can do is , you can collect the stream in a list and then you can call the map and forEach functions which accepts a lambda.
List<String> list =
Stream.of("test")
.filter(s -> s.startsWith("a"))
.collect(Collectors.toList());
list.forEach(item -> item);
list.map(item -> item);
Upvotes: 0
Reputation: 46
Java 8 Streams are not really reactive, although they are quite functional. There are no multiple terminal operations. The answer mentioning Supplier while letting you write code that looks like there are multiple terminal operations, they are terminals on totally different streams that were generated independently. That is, the time complexity hasn't changed. That is equivalent to writing
Stream getStream() {
return Stream.of(....);
}
static void main() {
Values values1 = getStream().collect();
Values values2 = getStream().collect();
}
The whole reason why you want multiple terminal operations is to save computation, not to make it look nice. Look at https://github.com/ReactiveX/RxJava that provides really reactive objects.
Upvotes: 0
Reputation: 6471
No you can't reuse a Stream
, but, if overloading heap space isn't a concern, you could save off the contents of the stream just before the terminal operation for reuse, using Stream.Builder
. for example:
Stream<OriginalType> myStream = ...
Stream.Builder<SomeOtherType> copy = Stream.builder();
List<SomeOtherType> aList = myStream
.filter(...)
.map(...) // eventually maps to SomeOtherType
.peek(copy) // pour values into a new Stream
.collect(Collectors.toList());
Set<SomeOtherType> aSet = copy.build()
.collect(Collectors.toSet());
One could keep chaining streams together, adding a new Stream.Builder
instance in each successive Stream
.
Not the answer you were looking for, but it does avoid the overhead of doing the pipeline operations a second time. It has its own weaknesses, being bound to heap space, but it doesn't have the weakness that Holger suggested in his comment on the Supplier
solution -- if it were a Random
stream, it would have the same values in the second iteration.
Upvotes: 1
Reputation: 368
Yes its a big NO in Java 8 streams to reuse a stream
For example for any terminal operation the stream closes when the operation is closed. But when we use the Stream in a chain, we could avoid this exception:
Normal terminal operation:
Stream<String> stream =
Stream.of("d2", "a2", "b1", "b3", "c")
.filter(s -> s.startsWith("a"));
stream.anyMatch(s -> true); // ok
stream.noneMatch(s -> true); // exception
But instead of this, if we use:
Supplier<Stream<String>> streamSupplier =
() -> Stream.of("d2", "a2", "b1", "b3", "c")
.filter(s -> s.startsWith("a"));
streamSupplier.get().anyMatch(s -> true); // ok
streamSupplier.get().noneMatch(s -> true); // ok
Here the .get()
"constructs" a new stream and NOT reuse whenever it hits this point.
Cheers!
Upvotes: 11