93

I'm learning the new Java 8 features, and while experimenting with streams (java.util.stream.Stream) and collectors, I realized that a stream can't be used twice.

Is there any way to reuse it?

Lii
  • 11,553
  • 8
  • 64
  • 88
Francisco
  • 951
  • 1
  • 6
  • 5

7 Answers7

93

If you want to have the effect of reusing a stream, you might wrap the stream expression in a Supplier and call myStreamSupplier.get() whenever you want a fresh one. For example:

Supplier<Stream<String>> sup = () -> someList.stream();
List<String> nonEmptyStrings = sup.get().filter(s -> !s.isEmpty()).collect(Collectors.toList());
Set<String> uniqueStrings = sup.get().collect(Collectors.toSet());
Lii
  • 11,553
  • 8
  • 64
  • 88
Hank D
  • 6,271
  • 2
  • 26
  • 35
  • 39
    This doesn't reuse the stream. The supplier just creates a new stream to be used each time it is invoked. – axiopisty Feb 21 '17 at 17:47
  • 40
    That's why Hank D said "the effect of". It's still cleaner and more reusable code. – Andrejs Apr 12 '17 at 08:29
  • 9
    Why would you create a supplier to create the stream, instead of just calling `someList.stream()` in places where you want such a stream? – Lii Jun 23 '18 at 21:04
  • 2
    Agree that this is not a bad idea if there is reason to believe some other lifting may be necessary by supplier, but I do think even "the effect of" is a little misleading. The benefit of reusing a stream is that it doesn't have to be created again, right? Not sure how much work is done in creating (retrieving?) a stream with `Collection::stream()`, but when I googled it, the effect I wanted, maybe without good need, was to minimize object instantiations. – Sammaron Jul 24 '18 at 21:18
  • 2
    from the `Supplier` java doc: **There is no requirement that a new or distinct result be returned each time the supplier is invoked.** – Blundell Nov 13 '18 at 21:54
  • 2
    Interesting suggestion, but I would even argue that this is perhaps not necessarily cleaner. In the case above sure, it's readable and fine, but what if the `sup.get()` call is further away in the code? Then I think `sup.get()` is misleading since it implies to the casual reader that the operation is perhaps cheaper than it is. So, I would argue that `someList.stream()` is the cleaner, more reusable form. – Per Lundberg Apr 15 '19 at 11:26
  • @Andrejs It's not cleaner or more reusable. It's fighting against the grain for no benefit. `sup` does not express what the supplier will return, while something like `listOfNames.stream()` is clear as day. – Vince Oct 30 '19 at 13:02
  • 1
    @Vince - two years on, I agree with you. "keep it simple stupid" is the way to go here. invoking `.stream()` multiple times is not really duplication, and suppliers-of-streams-of-lists-of-things are anything but readable. – Andrejs Oct 31 '19 at 10:15
  • ```Stream inputStream = Stream.of(arr); Supplier> supp = () -> inputStream; supp.get().forEach(System.out::println); supp.get().forEach(System.out::println); ``` this is causing same exception? can someone explain why? instead if i use ```Supplier> supp = () -> Stream.of(arr);``` then there is no error – Joel George Apr 25 '21 at 17:27
  • @JoelGeorge, in the former example you reuse the same inputStream which is not allowed, in the latter one you create new Stream on each `supp.get()` (because Stream.of(arr) is _invoked_, not just its result reused) – Nimtar May 07 '21 at 10:29
89

From the documentation:

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.

So the answer is no, streams are not meant to be reused.

Community
  • 1
  • 1
Vince
  • 14,470
  • 7
  • 39
  • 84
  • Aren't the words _should_ and _may_ somewhat weak for inferring the categorical _answer is no_? – Armali Sep 06 '19 at 06:47
  • 3
    @Armali You're right in the sense that those terms don't sound concrete. That's because `Stream` is an interface, and what I quoted above is *intended* behavior. However, implementers might break the contract while implementing. Users of `Stream` should expect the behavior I quoted above. That's why streams *should* only be operated on once. As for throwing an exception, that's up to the implementer. – Vince Sep 07 '19 at 15:10
  • 1
    @Armali: they *might* have oriented towards RFC2199: https://datatracker.ietf.org/doc/html/rfc2119: > SHOULD This word, or the adjective "RECOMMENDED", mean that there may exist valid reasons in particular circumstances to ignore a particular item, but the full implications must be understood and carefully weighed before choosing a different course. – serv-inc Sep 29 '21 at 13:38
51

As others have said, "no you can't".

But it's useful to remember the handy summaryStatistics() for many basic operations:

So instead of:

List<Person> personList = getPersons();

personList.stream().mapToInt(p -> p.getAge()).average().getAsDouble();
personList.stream().mapToInt(p -> p.getAge()).min().getAsInt();
personList.stream().mapToInt(p -> p.getAge()).max().getAsInt();

You can:

// Can also be DoubleSummaryStatistics from mapToDouble()

IntSummaryStatistics stats = personList.stream()
                                       .mapToInt(p-> p.getAge())
                                       .summaryStatistics();

stats.getAverage();
stats.getMin();
stats.getMax();
Andrejs
  • 10,803
  • 4
  • 43
  • 48
11

The whole idea of the Stream is that it's once-off. This allows you to create non-reenterable sources (for example, reading the lines from the network connection) without intermediate storage. If you, however, want to reuse the Stream content, you may dump it into the intermediate collection to get the "hard copy":

Stream<MyType> stream = // get the stream from somewhere
List<MyType> list = stream.collect(Collectors.toList()); // materialize the stream contents
list.stream().doSomething // create a new stream from the list
list.stream().doSomethingElse // create one more stream from the list

If you don't want to materialize the stream, in some cases there are ways to do several things with the same stream at once. For example, you may refer to this or this question for details.

Community
  • 1
  • 1
Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
5

As others have noted the stream object itself cannot be reused.

But one way to get the effect of reusing a stream is to extract the stream creation code to a function.

You can do this by creating a method or a function object which contains the stream creation code. You can then use it multiple times.

Example:

public static void main(String[] args) {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

    // The normal way to use a stream:
    List<String> result1 = list.stream()
        .filter(i -> i % 2 == 1)
        .map(i -> i * i)
        .limit(10)
        .map(i -> "i :" + i)
        .collect(toList());

    // The stream operation can be extracted to a local function to
    // be reused on multiple sources:
    Function<List<Integer>, List<String>> listOperation = l -> l.stream()
        .filter(i -> i % 2 == 1)
        .map(i -> i * i)
        .limit(10)
        .map(i -> "i :" + i)
        .collect(toList());

    List<String> result2 = listOperation.apply(list);
    List<String> result3 = listOperation.apply(Arrays.asList(1, 2, 3));

    // Or the stream operation can be extracted to a static method,
    // if it doesn't refer to any local variables:
    List<String> result4 = streamMethod(list);

    // The stream operation can also have Stream as argument and return value,
    // so that it can be used as a component of a longer stream pipeline:
    Function<Stream<Integer>, Stream<String>> streamOperation = s -> s
        .filter(i -> i % 2 == 1)
        .map(i -> i * i)
        .limit(10)
        .map(i -> "i :" + i);

    List<String> result5 = streamOperation.apply(list.stream().map(i -> i * 2))
        .filter(s -> s.length() < 7)
        .sorted()
        .collect(toCollection(LinkedList::new));
}

public static List<String> streamMethod(List<Integer> l) {
    return l.stream()
        .filter(i -> i % 2 == 1)
        .map(i -> i * i)
        .limit(10)
        .map(i -> "i :" + i)
        .collect(toList());
}

If, on the other hand, you already have a stream object which you want to iterate over multiple times, then you must save the content of the stream in some collection object.

You can then get multiple streams with the same content from than collection.

Example:

public void test(Stream<Integer> stream) {
    // Create a copy of the stream elements
    List<Integer> streamCopy = stream.collect(toList());

    // Use the copy to get multiple streams
    List<Integer> result1 = streamCopy.stream() ...
    List<Integer> result2 = streamCopy.stream() ...
}
Lii
  • 11,553
  • 8
  • 64
  • 88
2

Come to think of it, this will of "reusing" a stream is just the will of carry out the desired result with a nice inline operation. So, basically, what we're talking about here, is what can we do to keep on processing after we wrote a terminal operation?

1) if your terminal operation returns a collection, the problem is solved right away, since every collection can be turned back into a stream (JDK 8).

List<Integer> l=Arrays.asList(5,10,14);
        l.stream()
            .filter(nth-> nth>5)
            .collect(Collectors.toList())
            .stream()
            .filter(nth-> nth%2==0).forEach(nth-> System.out.println(nth));

2) if your terminal operations returns an optional, with JDK 9 enhancements to Optional class, you can turn the Optional result into a stream, and obtain the desired nice inline operation:

List<Integer> l=Arrays.asList(5,10,14);
        l.stream()
            .filter(nth-> nth>5)
            .findAny()
            .stream()
            .filter(nth-> nth%2==0).forEach(nth-> System.out.println(nth));

3) if your terminal operation returns something else, i really doubt that you should consider a stream to process such result:

List<Integer> l=Arrays.asList(5,10,14);
        boolean allEven=l.stream()
            .filter(nth-> nth>5)
            .allMatch(nth-> nth%2==0);
        if(allEven){
            ...
        }
BabaNew
  • 884
  • 1
  • 13
  • 27
2

The Functional Java library provides its own streams that do what you are asking for, i.e. they're memoized and lazy. You can use its conversion methods to convert between Java SDK objects and FJ objects, e.g. Java8.JavaStream_Stream(stream) will return a reusable FJ stream given a JDK 8 stream.

Bill Burdick
  • 935
  • 10
  • 15