Stream.peek() method in Java 8 vs Java 9
Asked Answered
M

2

75

I am in the progress of learning through Java 8 lambda expressions and would like to ask about the following piece of Java code relating to the peek method in the function interface that I have come across.

On execution of the program on IDE, it gives no output. I was expecting it would give 2, 4, 6.

import java.util.Arrays;
import java.util.List;

public class Test_Q3 {

    public Test_Q3() {
    }

    public static void main(String[] args) {
        List<Integer> values = Arrays.asList(1, 2, 3);
        values.stream()
              .map(n -> n * 2)
              .peek(System.out::print)
              .count();
    }
}
Multipartite answered 12/1, 2018 at 7:51 Comment(3)
Perhaps the java-8 tag should be replaced with java-9Ambitious
added java-9. I think it should be both, since it is about differences between the two.Largehearted
Good difference to learn between java8 and java9. Generalising the title of this question... On another note, this is pretty much covered by Holger in this answer.Hypnotism
M
68

I assume you are running this under Java 9? You are not altering the SIZED property of the stream, so there is no need to execute either map or peek at all.

In other words all you care is about count as the final result, but in the meanwhile you do not alter the initial size of the List in any way (via filter for example or distinct) This is an optimization done in the Streams.

Btw, even if you add a dummy filter this will show what you expect:

values.stream ()
      .map(n -> n*2)
      .peek(System.out::print)
      .filter(x -> true)
      .count();
Multiversity answered 12/1, 2018 at 7:54 Comment(9)
…and now imagine a future version that is capable of analyzing a Predicate’s code to predict its outcome in advance, i.e whether it will be never or always fulfilled for a particular stream pipeline…Vivian
So the moral is, don't use count as a way to force stream execution for side effects.Incubation
@Incubation no, the moral is, don’t use peek as a way to force stream execution for side effects (other than debugging). If you want to apply an action to every stream element, use forEach.Vivian
@Vivian But what if you want to apply an action AND get the filtered result from the stream using a collect()? There isn't an alternative to peek if you want to do it in a single chain.Darnell
@Darnell care to provide an example to make your question cleaner?Multiversity
@Darnell “do it in a single chain” is not an actual goal. You don’t notice a difference when you collect into a list first and perform your action via forEach on that result list. Besides that, there is no sense in saying “there isn't an alternative to peek” when this Q&A already demonstrates why peek isn’t an alternative at all.Vivian
@Vivian In the current codebase there is a list of things which I filter, then for each item some additional checks are done that might throw a checked exception (yes, old code, yay) via void procedures and finally that item is added to a result collection and that collection is returned. To try and fit that existing code into a single chain of java stream calls, I need the peek method. But actually I realised those additional checks should be done in a filter, not in a void procedure.Darnell
@Darnell how about posting a question about this? people here might be very helpful sometimes...Multiversity
@Darnell as already said, “to try and fit that existing code into a single chain of java stream calls” is an artificial goal. Even rewriting the code to use a stream is not a useful goal, especially when this rewrite doesn’t even produce clean code. As said as well, you don’t need peek anyway, as it makes no difference when you perform the checks afterwards on the result collection. Especially, as such subsequent test can throw these checked exceptions, unlike peek.Vivian
A
65

Here's some relevant quotes from the Javadoc of Stream interface:

A stream implementation is permitted significant latitude in optimizing the computation of the result. For example, a stream implementation is free to elide operations (or entire stages) from a stream pipeline -- and therefore elide invocation of behavioral parameters -- if it can prove that it would not affect the result of the computation. This means that side-effects of behavioral parameters may not always be executed and should not be relied upon, unless otherwise specified (such as by the terminal operations forEach and forEachOrdered). (For a specific example of such an optimization, see the API note documented on the count() operation. For more detail, see the side-effects section of the stream package documentation.)

And more specifically from the Javadoc of count() method:

API Note:

An implementation may choose to not execute the stream pipeline (either sequentially or in parallel) if it is capable of computing the count directly from the stream source. In such cases no source elements will be traversed and no intermediate operations will be evaluated. Behavioral parameters with side-effects, which are strongly discouraged except for harmless cases such as debugging, may be affected. For example, consider the following stream:

List<String> l = Arrays.asList("A", "B", "C", "D");
long count = l.stream().peek(System.out::println).count();

The number of elements covered by the stream source, a List, is known and the intermediate operation, peek, does not inject into or remove elements from the stream (as may be the case for flatMap or filter operations). Thus the count is the size of the List and there is no need to execute the pipeline and, as a side-effect, print out the list elements.

These quotes only appear on the Javadoc of Java 9, so it must be a new optimization.

Ambitious answered 12/1, 2018 at 8:1 Comment(3)
noteworthy also is that javadoc of count() does not say the same in java 8.Largehearted
From the docs of Stream.peek --> 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.Hypnotism
@nullpointer that’s also only in the Java 9 documentation, though it was always implied.Vivian

© 2022 - 2024 — McMap. All rights reserved.