Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Non-terminal forEach() in a stream?

Sometimes when processing a Java stream() I find myself in need of a non-terminal forEach() to be used to trigger a side effect but without terminating processing.

I suspect I could do this with something like .map(item -> f(item)) where the method f performs the side effect and returns the item to the stream, but it seems a tad hokey.

Is there a standard way of handling this?

like image 512
Ian Avatar asked Nov 16 '16 05:11

Ian


People also ask

Can we use forEach in streams Java?

Stream forEach() method in Java with examplesStream forEach(Consumer action) performs an action for each element of the stream. Stream forEach(Consumer action) is a terminal operation i.e, it may traverse the stream to produce a result or a side-effect.

Is forEach a terminal operation?

forEach() on List. The forEach() method is a terminal operation, which means that after we call this method, the stream along with all of its integrated transformations will be materialized.

Can we have stream without terminal operation?

Avoiding the Use of the Reduce MethodA stream does not process any data if it does not end with a terminal operation. We already covered the terminal operation reduce() , and you saw several terminal operations in other examples. Let us now present the other terminal operations you can use on a stream.

What happens if you do not call a terminal operation on a stream?

Non-Terminal operation : will transform a stream into another stream, such as filter(Predicate).


2 Answers

Yes there is. It is called peek() (example from the JavaDoc):

Stream.of("one", "two", "three", "four")      .peek(e -> System.out.println("Original value: " + e))      .filter(e -> e.length() > 3)      .peek(e -> System.out.println("Filtered value: " + e))      .map(String::toUpperCase)      .peek(e -> System.out.println("Mapped value: " + e))      .collect(Collectors.toList()); 
like image 180
Thomas Kläger Avatar answered Sep 28 '22 05:09

Thomas Kläger


No, there is not.

peek() will only operate on all elements when forced to by a following operation. Can you predict what will be printed by this code?

public class Test {     private static final AtomicBoolean FLAG = new AtomicBoolean(false);      private static void setFlagIfGreaterThanZero(int val) {         if (val > 0) {             FLAG.set(true);         }     }      public static void main(String[] args) {         // Test 1         FLAG.set(false);         IntStream.range(0, 10)                  .peek(Test::setFlagIfGreaterThanZero)                  .findFirst();         System.out.println(FLAG.get());          // Test 2         FLAG.set(false);         IntStream.range(0, 10)                  .peek(Test::setFlagIfGreaterThanZero)                  .sorted()                  .findFirst();         System.out.println(FLAG.get());          // Test 3         FLAG.set(false);         IntStream.range(0, 10)                  .peek(Test::setFlagIfGreaterThanZero)                  .filter(x -> x == 0)                  .toArray();         System.out.println(FLAG.get());          // Test 4         FLAG.set(false);         IntStream.range(0, 10)                  .boxed()                  .peek(Test::setFlagIfGreaterThanZero)                  .sorted()                  .findFirst();         System.out.println(FLAG.get());     } } 

The answer is:

false
false
true
true

That output might be intuitive if you have a solid understanding of Java Streams, but hopefully it also indicates that it's a very bad idea to rely on peek() as a mid-stream forEach().

map() also suffers the same issue. As far as I'm aware, there is no Stream operation that guarantees a sort of "process every element without taking shortcuts" behavior in every case independent of the prior and following operations.

Although this can be a pain, the short-circuiting behavior of Streams is an important feature. You might find this excellent answer to another question on this topic to be useful.

like image 33
Matthew Read Avatar answered Sep 28 '22 05:09

Matthew Read