How can I terminate a Stream if I don't need any value from termination?
Asked Answered
S

4

10

I have a Stream that does all processing in peek() methods. I don't need any result from the Stream, but I need a terminal operation in order for processing to occur. Of course I can terminate the Stream with count() (or any other operation), but that would be misleading as if I needed some result from Stream termination. What is the correct method to terminate the Stream in such case?

Here is the code for reference:

Stream<Collection<NetworkPart>> graphHolders = cutSegment.stream()
    .map(this::obtainCollectionFor);
for (NetworkPart part : edgesToNetworkParts.get(originalSegment)) {
    part.integrate(cutSegment);
    graphHolders = graphHolders.peek(gh -> gh.add(part));
}
graphHolders.count(); // Just to terminate
Swanner answered 17/2, 2015 at 14:47 Comment(2)
Why not put all the parts in a List and then do graphHolders.forEach(gh -> gh.addAll(theParts))? It seems you only have a need for this because of your use of peek.Darleen
@Radiodef, thank you very much, that was the problem indeed. I updated my own answer with the correct peek()-less code.Swanner
S
5

This is how I've rewritten the code from the question:

Collection<NetworkPart> partsOwningEdge = edgesToNetworkParts.get(originalSegment);
partsOwningEdge.forEach(part -> part.integrate(cutSegment));
cutSegment.stream()
    .map(this::obtainCollectionFor)
    .forEach(gh -> gh.addAll(partsOwningEdge));
Swanner answered 17/2, 2015 at 15:32 Comment(0)
S
3

You could use allMatch() as a terminal operation as this does not need to be evaluated on all elements.

Satisfactory answered 17/2, 2015 at 15:11 Comment(3)
This would be the same as using count(). I don't need the boolean value returned by allMatch()Swanner
No, because count() needs to visit every element in the stream, while allMatch() only needs to visit one, non-matching element.Satisfactory
So it would be a construction like streamToTerminate.allMatch(a->false);, which is even uglier. The purpose of the question is to find out the solution that is the best at showing programmer's intent to terminate the stream without returning any value. .allMathc(a->false) is even worse than count() with such requirements for a terminal operation.Swanner
C
3

If it's just the 'misleading' that's irking you, try this:

private static void terminateStream(Stream<?> stream) { 
    stream.forEach(e -> {});
}

I think this would remove any ambiguity: The method has a clear purpose and the call is clear as well. When used as follows,

public static void main(String[] args) {
    Stream<String> stream = Arrays.asList("a", "b", "c").stream();
    stream = stream.peek(str -> System.out.println(str));
    terminateStream(stream);
}

The main method will print

a
b
c

This does require that stream is not closed and has not been operated upon, or it will throw an IllegalStateException.

Callender answered 17/2, 2015 at 15:11 Comment(3)
This of course does the job, but that would be writing another method, and methods are supposed to be in the scope where they can be useful. This method can be useful anywhere, so I'd expect to find it in the standard library. There is Function.identity() for a similar case with mapping; is there a standard Consumer that does nothing?Swanner
@Suseika I haven't found any default implementations of Consumer in the standard library. You could always just place it as far up in your project hierarchy as possible.Callender
Yup, I guess that is the best one can do.Swanner
P
0

Take care when calling "intermediate" stream operations, especially in combination with peek. From the JavaDoc:

"In cases where the stream implementation is able to optimize away the production of some or all the elements (such as with short-circuiting operations like findFirst, or in the example described in count), the action will not be invoked for those elements."

Your "terminating" count() actually prevents the peek from beeing called at all.

Try running this version:

List<AtomicInteger> ints = List.of(new AtomicInteger(0), new AtomicInteger(0));

Stream<AtomicInteger> streamedInts = ints.stream();
for (int i=0; i<5; i++) {
    streamedInts = streamedInts.peek(AtomicInteger::incrementAndGet);
}
streamedInts.count();

ints.forEach(foo -> System.out.println(foo.get()));

then replace the ".count()" call to something really terminating like the suggested ".forEach(e -> {})" of @DennisW

But a much better approach would be to use the terminating forEach to actually modify the elements as @gvlasov already suggested.

Note: i would comment another answer, but sadly im not allowed yet :|

Plossl answered 15/4, 2024 at 15:46 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.