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();
    }
}
Lii
  • 11,553
  • 8
  • 64
  • 88
Patrick C.
  • 1,339
  • 3
  • 16
  • 31
  • 4
    Perhaps the java-8 tag should be replaced with java-9 – Eran Jan 12 '18 at 08:07
  • 9
    added java-9. I think it should be both, since it is about differences between the two. – eis Jan 12 '18 at 08:14
  • 9
    Good difference to learn between [tag:java8] and [tag:java9]. Generalising the title of this question... On another note, this is pretty much covered by Holger in [this answer](https://stackoverflow.com/a/33636377/1746118). – Naman Jan 12 '18 at 10:27

2 Answers2

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();
Lii
  • 11,553
  • 8
  • 64
  • 88
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • 20
    …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… – Holger Jan 12 '18 at 11:47
  • 4
    So the moral is, don't use `count` as a way to force stream execution for side effects. – puhlen Jan 12 '18 at 15:21
  • 29
    @puhlen 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`. – Holger Jan 12 '18 at 16:30
  • @Holger 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. – klaar Sep 10 '18 at 13:52
  • @klaar care to provide an example to make your question cleaner? – Eugene Sep 10 '18 at 14:07
  • 2
    @klaar “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. – Holger Sep 11 '18 at 08:38
  • @Holger 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. – klaar Sep 11 '18 at 15:07
  • @klaar how about posting a question about this? people here might be very helpful sometimes... – Eugene Sep 11 '18 at 15:08
  • 3
    @klaar 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`. – Holger Sep 12 '18 at 06:07
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.

Eran
  • 387,369
  • 54
  • 702
  • 768
  • 9
    noteworthy also is that [javadoc of count()](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#count--) does not say the same in java 8. – eis Jan 12 '18 at 08:06
  • 7
    From the docs of [`Stream.peek`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#peek-java.util.function.Consumer-) --> *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.* – Naman Jan 12 '18 at 10:18
  • 4
    @nullpointer that’s also only in the Java 9 documentation, though it was always implied. – Holger Jan 12 '18 at 11:51