Is there any way to reuse a Stream?

JavaJava StreamReusability

Java Problem Overview


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?

Java Solutions


Solution 1 - Java

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.

Solution 2 - Java

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

Solution 3 - Java

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

Solution 4 - Java

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.

Solution 5 - Java

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() ...
}

Solution 6 - Java

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 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 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 l=Arrays.asList(5,10,14); boolean allEven=l.stream() .filter(nth-> nth>5) .allMatch(nth-> nth%2==0); if(allEven){ ... }

Solution 7 - Java

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionFranciscoView Question on Stackoverflow
Solution 1 - JavaDioxinView Answer on Stackoverflow
Solution 2 - JavaHank DView Answer on Stackoverflow
Solution 3 - JavaAndrejsView Answer on Stackoverflow
Solution 4 - JavaTagir ValeevView Answer on Stackoverflow
Solution 5 - JavaLiiView Answer on Stackoverflow
Solution 6 - JavaBabaNewView Answer on Stackoverflow
Solution 7 - JavaBill BurdickView Answer on Stackoverflow