4

I want to terminate execution of stream when found first value. However, when I run code below it shows up that two methods are called even if value is present from first method.

public static void main(String[] args) {

        Optional<String> s = Stream.of(first(), second())
                .filter(Optional::isPresent)
                .findFirst()
                .flatMap(Function.identity());

        System.out.println(s.get());
    }

    private static Optional<String> first() {
        System.out.println("first");
        return Optional.of("someValue");
    }

    private static Optional<String> second() {
        System.out.println("second");
        return Optional.empty();
    }

I checked in docs that:

  1. isPresent

Return {@code true} if there is a value present, otherwise {@code false}.

  1. findFirst()

Returns an {@link Optional} describing the first element of this stream, or an empty {@code Optional} if the stream is empty. If the stream has no encounter order, then any element may be returned.

So the first condition it met, and the second seems to be also fulfilled cause it returns:

first
second
someValue

How to quit execution if first value present, and not execute second method?

Naman
  • 27,789
  • 26
  • 218
  • 353
Bartek
  • 2,109
  • 6
  • 29
  • 40

2 Answers2

6

How to quit execution if first value present, and not execute second method?

Stream#findFirst is a short-circuiting terminal operation. However, the methods are being invoked when you call Stream.of(first(), second()). This can be proven with the following snippet:

Optional<String> s = Stream.of(first(), second())
                           .peek($ -> System.out.println("Hello World!"))
                           .filter(Optional::isPresent)
                           .findFirst()
                           .flatMap(Function.identity());

System.out.println(s.get());

Output:

first
second
Hello World!
someValue

To prevent first() and second() from executing when calling Stream#of, one solution would be to wrap them in a Supplier<Optional<String>>:

Stream<Supplier<Optional<String>>> stream = Stream.of(Test::first, Test::second);

Optional<String> s = stream.map(Supplier::get)
                           .filter(Optional::isPresent)
                           .findFirst()
                           .flatMap(Function.identity());

System.out.println(s.get());

Output:

first
someValue
Jacob G.
  • 28,856
  • 5
  • 62
  • 116
  • 1
    `Stream>> supplierStream = Stream.of(Test::first, Test::second); Optional s = supplierStream...` can help readability there. – Naman Feb 12 '19 at 17:28
  • 1
    Good point, I'm not a big fan of the cast as well. I'll change it :) – Jacob G. Feb 12 '19 at 17:46
  • Instead of `.findFirst() .flatMap(Function.identity())` you can also use `.findFirst() .orElse(Optional.empty())` or `.map(Optional::get) .findFirst()`. Seems to be a [recurring question](https://stackoverflow.com/a/28833677/2711488), but finding it via Stackoverflow’s search function is indeed really hard… – Holger Feb 13 '19 at 11:09
2

The problem is not that findFirst is not short-circuiting, but that Stream.of(first(), second()) causes immediate (not delayed) execution of first() and second. It's essentially evaluated as any ordinary expression.

In other words, even if your main method is just

final public static void main(String[] args) throws Exception {
    Stream.of(first(), second());
}

It would still execute first() and second() immediately.

ernest_k
  • 44,416
  • 5
  • 53
  • 99