考虑这段代码
Object found = collection.stream()
.filter( s -> myPredicate1(s))
.filter( s -> myPredicate2(s))
.findAny()
它会处理整个流,并为集合的所有元素调用myPredicate1
和myPredicate2
吗?或者,实际找到该值需要调用多少个谓词?
答案 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#findAny是short-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();
}
}