4

As simple as:

import java.util.stream.*;

public class MyClass {
  public static void main(String args[]) {
    Long x = Stream.of(1, 2, 3).map(i -> {
      System.out.println(i);
      return i + 4;
    }).count();

    System.out.println(x); // prints '3'
  }
}

The count() here is used in order to trigger the intermediate operations which include System.out.println(i), but nothing gets printed from inside map(). Why is that?

Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
microwth
  • 1,016
  • 1
  • 14
  • 27
  • 5
    Can you find the documentation that says the map function needs to be called? This is why you don't use side effects in your streams. – matt May 16 '23 at 12:30
  • I get "1, 2, 3, 3" as output. – Mr. Polywhirl May 16 '23 at 12:31
  • 3
    @Mr.Polywhirl Probably depends on the compiler / JIT you're running it on. This is due to an optimization. – Maarten Bodewes May 16 '23 at 12:33
  • 3
    @MaartenBodewes it’s a well known optimization of the Stream API implementation introduced in Java 9 and *the* canonical example of why programmers should make no assumptions about the evaluation of intermediate operations. Since it was expected that developers get surprised, [the documentation](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/util/stream/Stream.html#count()) even mentions this. No JIT magic involved. – Holger May 16 '23 at 14:52
  • Well, it makes sense in functional programming (which the Stream API basically implements). However, I can imagine that it still wrong-foot users of Java, which are used to more OO / procedural ways of handling things. Just skipping over methods that introduce side effects is *generally* not how Java works. Or used to work anyway. – Maarten Bodewes May 16 '23 at 14:56
  • 2
    @MaartenBodewes imperative code continues to work as it used to do. The Stream API is specifically for specifying an operation in a functional/declarative way and accepting that there is less control over the evaluation. The problem here is not that the implementation skips over a method with side effects [but that this function has a side effect to begin with](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/util/stream/package-summary.html#SideEffects). Loops still exists and are neither deprecated nor discouraged; they are still the preferred approach for side effects. – Holger May 17 '23 at 10:18
  • Uh, that's what I said right? Yes, when using Stream API, assume & use functional programming. – Maarten Bodewes May 17 '23 at 11:08

3 Answers3

11

The javadoc for count() has note that:

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.

DuncG
  • 12,137
  • 2
  • 21
  • 33
  • 2
    That's an update from [jdk8](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#count--) – matt May 16 '23 at 12:48
  • 1
    @matt this update was introduced with [Java 9](https://docs.oracle.com/javase/9/docs/api/java/util/stream/Stream.html#count--) – Thomas Kläger May 16 '23 at 13:01
9

Streams are lazy and smart. Since your terminal operation is count, which does not care of individual elements, but only of their number, map isn't called.
Replace count with something that would need to calculate individual elements, like sum - and map will be called.

Long x = Stream.of(1, 2, 3).map(i -> {
            System.out.println(i);
            return i + 4;
        })
        .mapToLong(Long::valueOf)
        .sum(); // prints 1 2 3

        System.out.println(x); // prints 18

Also a person in comments rightfully reminds you side effects.

ILya Cyclone
  • 806
  • 6
  • 16
1

Directly calling count actually not helps to run the map. but if we try to call the toList and Size for the count. seems working for me.

Integer x = Stream.of(1, 2, 3).map(i -> {
                    System.out.println(i);
                    return i + 4;
                })
                .toList().size();
J.K
  • 2,290
  • 1
  • 18
  • 29
  • I guess that this requires a clear DEBUG comment though, because the next programmer may simply use `count()` again, or the compiler may get "smarter" and still skip the `println` statement. That said, if you perform `println` in a *function* a "TODO fix" comment should already be there. – Maarten Bodewes May 16 '23 at 15:00