Is there any way to reuse a Stream? [duplicate]
Asked Answered
A

7

98

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?

Alleviative answered 28/3, 2016 at 2:22 Comment(1)
You may find here an alternative choice: dzone.com/articles/how-to-replay-java-streamsOrlon
N
98

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());
Newspaper answered 28/3, 2016 at 13:49 Comment(10)
This doesn't reuse the stream. The supplier just creates a new stream to be used each time it is invoked.Vilhelmina
That's why Hank D said "the effect of". It's still cleaner and more reusable code.Spina
Why would you create a supplier to create the stream, instead of just calling someList.stream() in places where you want such a stream?Seabee
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.Darnel
from the Supplier java doc: There is no requirement that a new or distinct result be returned each time the supplier is invoked.Pascal
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.Minimum
@Spina 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.Cottar
@Cottar - 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.Spina
Stream<String> inputStream = Stream.of(arr); Supplier<Stream<String>> 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<Stream<String>> supp = () -> Stream.of(arr); then there is no errorSheldonshelduck
@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)Anesthetic
C
92

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.

Cottar answered 28/3, 2016 at 2:32 Comment(3)
Aren't the words should and may somewhat weak for inferring the categorical answer is no?Marlowe
@Marlowe 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.Cottar
@Armali: they might have oriented towards RFC2199: 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.Scandinavian
S
53

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();
Spina answered 12/4, 2017 at 8:46 Comment(1)
Nice addition to the question. I was not aware of this and it has proven to be very handy!Tautomerism
U
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.

Un answered 28/3, 2016 at 10:18 Comment(0)
S
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() ...
}
Seabee answered 4/6, 2018 at 10:12 Comment(0)
R
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){
            ...
        }
Ringmaster answered 4/6, 2018 at 9:42 Comment(0)
T
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.

Threonine answered 14/11, 2018 at 13:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.