9

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?

Mehraj Malik
  • 14,872
  • 15
  • 58
  • 85
  • 4
    that's a cold hard fact: you can't re-use streams. You can take your *input* that you obtained the stream from, and stream again. Also depending on *the exact problem* that you are facing, you might obtain the desired result by streaming only once. – Eugene Mar 02 '17 at 10:23
  • 1
    see http://stackoverflow.com/questions/28459498/why-are-java-streams-once-off for a rationale – user140547 Mar 02 '17 at 10:55
  • Why do you want to reuse them in the first place? What are you hoping to accomplish by doing this? – Rogue Mar 02 '17 at 12:17
  • @Rogue, was just thinking if is it possible. – Mehraj Malik Mar 02 '17 at 12:36
  • well okay then: no. – Rogue Mar 02 '17 at 17:19

5 Answers5

11

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!

Vijayan Kani
  • 368
  • 3
  • 9
  • 4
    Well, that's a workaround but not what was asked for: `what if we want to reuse *the same stream* more than once` – Markus Benko Mar 02 '17 at 10:17
  • That's not the re-usability. Please read your last line about **get()**. – Mehraj Malik Mar 02 '17 at 10:20
  • In Java 8 streams cannot be reused basically, not really in a terminal operation. The answer is NO. – Vijayan Kani Mar 02 '17 at 10:25
  • 3
    @VijayanKani I actually like the Supplier in this case.. might be useful, but you need to update the answer that clearly says - it's not possible, but you could do a supplier... – Eugene Mar 02 '17 at 10:37
  • +1 for your efforts. However, disappointed to hear that Java is not letting us **reuse** the streams. STRANGE!!! – Mehraj Malik Mar 02 '17 at 10:46
  • 2
    @Mehraj Malik: just consider that Streams are not always backed by a Collection. E.g., you may create a Stream from a `Random` instance or from a network channel, so what should happen when you try to reuse such a stream, a) getting different values each time, yielding to inconsistent results or b) the stream should buffer all values, just for the case that it is used a second time? – Holger Mar 02 '17 at 11:23
1

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.

Hank D
  • 6,271
  • 2
  • 26
  • 35
0

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.

Joel Lim
  • 46
  • 1
  • 3
0

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);
0

A naive example of 3 different terminal-like operations on stream elements at once:

  • displaying them,
  • counting,
  • calculating their sum

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);
ktp
  • 126
  • 8