Stream.findAny是短路操作吗?

时间:2017-05-25 12:03:21

标签: java java-8 java-stream short-circuiting

考虑这段代码

Object found = collection.stream()
    .filter( s -> myPredicate1(s))
    .filter( s -> myPredicate2(s))
    .findAny()

它会处理整个流,并为集合的所有元素调用myPredicate1myPredicate2吗?或者,实际找到该值需要调用多少个谓词?

5 个答案:

答案 0 :(得分:5)

是的,正如Stream.findAny()文档所述:

  

这是一种短路终端操作。

一种常见的误解是流中的对象被“推”到消费操作上。它实际上是另一种方式 - 消耗操作拉动每个元素。

对于顺序流,只有在查找匹配值时才会调用许多谓词。 并行流可以执行更多谓词,但也会在找到元素后立即停止执行。

public class StreamFilterLazyTest {

  static int stI = 0;

  static class T { 

    public T() {
      super();
      this.i = ++stI;
    }

    int i;

    int getI() {
      System.err.println("getI: "+i);
      return i;
    }
  }

  public static void main(String[] args) {
    T[] arr = {new T(), new T(), new T(), new T(), new T(), new T(), new T(), new T(), new T(), new T()};
    Optional<T> found = Arrays.stream(arr).filter(t -> t.getI() == 3).findAny();
    System.out.println("Found: "+found.get().getI());
  }
}

将打印:

getI: 1
getI: 2
getI: 3
Found: 3

答案 1 :(得分:4)

findAny()州的javadoc

  

&#34;这是一种短路终端操作。&#34;

     

&#34;此操作的行为明确是不确定的;可以自由选择流中的任何元素。这是为了在并行操作中实现最大性能......&#34;

这意味着顺序流上的findAny()只会&#34;拉&#34;足够的元素来找到第一个。在并行流上,根据实现情况,它可以提取得足够多。

javadoc还声明:

  

&#34;中间操作返回一个新流。他们总是懒惰;执行诸如filter()的中间操作实际上不执行任何过滤,而是创建一个新流,当遍历时,该流包含与给定谓词匹配的初始流的元素。在执行管道的终端操作之前,不会开始遍历管道源。&#34;

这意味着filter()谓词仅在findAny()终端拉出它们时发生。

简而言之:

  

问:filter + findAny仍然是短路操作吗?

答:是的。

答案 2 :(得分:2)

如果使用顺序或并行流并不重要,它们仍将遍历尽可能多的元素以找到匹配的第一个元素。如果您使用findFirst并且您有一个由有序集合组成的Stream,则可能会有所不同。

在这种情况下,

findFirst必须保留订单。

在这种情况下,由于并行性,第二个元素,然后第三个元素可能在之前处理,但仍然只返回第一个元素

答案 3 :(得分:0)

Stream#findAnyshort-circuiting terminal operation。它将访问Predicate以匹配&amp;由于Stream#filter每次都返回一个新流,因此一个接一个地短路。

  

中间操作返回一个新流。他们总是懒惰;执行诸如filter()之类的中间操作实际上并不执行任何过滤,而是创建一个新流,当遍历时,该流包含与给定谓词匹配的初始流的元素。在执行管道的终端操作之前,不会开始遍历管道源。

正如@Holger在评论中提到的,它可以使过滤器短路,如下所示:

if(predicate1.test(value) && predicate2.test(value)){
 ....
}

测试

Iterator<Predicate<Integer>> predicates = Stream.<Predicate<Integer>>of(
        it -> false,
        it -> {
            throw new AssertionError("Can't be short-circuited!");
        }
).iterator();

Predicate<Integer> expectsToBeShortCircuited = it -> predicates.next().test(it);

Stream.of(1).filter(expectsToBeShortCircuited).filter(expectsToBeShortCircuited)
      //                                       |
      //                                       |
      //                      here is short-circuited since the stream is empty now
      .findAny();

答案 4 :(得分:0)

您可以使用peek来验证此

  

==顺序==

     

Alpha1 Alpha2 Beta1 Beta2 Gamma1 Gamma2 Dolphin1 Fargo1 Fargo2发现:   Fargo应用:9

     

==平行==
  Arnold1 Jim1 Loke1 Alpha1 Mustard1 Lenny1 Mustard2 Mark1 Alpha2 Mark2   Beta1 Beta2 Gamma1 Fargo1 Gamma2 Dolphin1 Fargo2发现:Fargo   应用:17

YMMV取决于核心数量等。

由下面制作

package test.test;

import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;

public class Snippet {

    static AtomicInteger predicateApplications;

    public static void main(String arr[]) {
        System.out.println("== Sequential == \n");
        sequential();
        System.out.println(" == Parallel == \n");
        parallel();
    }

    private static void sequential() {
        Stream<String> stream = Stream.of("Alpha", "Beta", "Gamma", "Dolphin", "Fargo", "Mustard", "Lenny", "Mark",
                "Jim", "Arnold", "Loke");
        execute(stream);
    }

    private static void parallel() {
        Stream<String> parallelStream = Stream
                .of("Alpha", "Beta", "Gamma", "Dolphin", "Fargo", "Mustard", "Lenny", "Mark", "Jim", "Arnold", "Loke")
                .parallel();
        execute(parallelStream);
    }

    private static void execute(Stream<String> stream) {
        predicateApplications = new AtomicInteger(0);

        Optional<String> findAny = stream.peek(s -> print(s + "1")).filter(s -> s.contains("a"))
                .peek(s -> print(s + "2")).filter(s -> s.startsWith("F")).findAny();

        String found = findAny.orElse("NONE");
        System.out.println("\nFound: " + found);
        System.out.println("Applications: " + predicateApplications.get());
    }

    private static void print(String s) {
        System.out.print(s + " ");
        predicateApplications.incrementAndGet();
    }

}