0%

stream and lambda(12) - 终止操作之查找与匹配(findAny、findFirst、allMatch、anyMatch、noneMatch)

Stream 提供的一系列方法,在经过中间操作之后,最后还是为了得到确定的元素。因此,Stream 还提供了大量的终止操作,以便我们能得到想到的数据。

三个 Match 操作

方法定义

boolean allMatch(Predicate<? super T> predicate)
boolean anyMatch(Predicate<? super T> predicate)
boolean noneMatch(Predicate<? super T> predicate)

*Match 方法有三个:allMatch 表示所有元素都要通过 Predicate 的考验,anyMatch 表示只要有任何一个元素通过考验就行,noneMatch 表示没有任何一个掉进 Predicate 的陷阱才可以。

看起来是不是有些熟悉?没错,是有点像 &&||! 的条件表达式。

使用举例

1
2
3
4
5
6
7
8
9
public void matchTest(){
List<Integer> integers = Arrays.asList(1, 23, 4, 5, 6, 7, 0);
//所有元素都大于5
System.out.println(integers.stream().allMatch(i -> i > 5));
//存在某个元素大于5
System.out.println(integers.stream().anyMatch(i -> i > 5));
//没有任何元素大于5
System.out.println(integers.stream().noneMatch(i -> i > 5));
}

不出意料,三次分别输出的是 false, true, false

Stream 内没有任何元素呢?

出乎意料的结果

api 的使用太过简单,膨胀的我忽然很好奇一个问题:如果 Stream 里没有任何元素呢?会不会报错?运行结果又如果呢?

1
2
3
4
5
6
public void emptyStreamMatchTest(){
List<Integer> integers = Collections.emptyList();
System.out.println(integers.stream().allMatch(i -> i > 5));
System.out.println(integers.stream().anyMatch(i -> i > 5));
System.out.println(integers.stream().noneMatch(i -> i > 5));
}

运行之前,根据经验先预测一下:运行无异常,结果分别是 false, false, true。理由很简单,没有元素,所以不会满足所有元素都大于5,也就是没有元素大于5。

运行一下,结果如下:

true, false, true

哎呀妈呀,我和我的小伙伴都惊呆了,没有任何元素,怎么会是 allMatch呢!这他妈即兴一试还有意外收获!!此刻的我好开心呀:我发现 JDK 源码的 bug 了!!!

一探源码查究竟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// allMatch 方法实现
public final boolean allMatch(Predicate<? super P_OUT> predicate) {
return evaluate(MatchOps.makeRef(predicate, MatchOps.MatchKind.ALL));
}

// 三个 match 方法实际都用到了这里
public static <T> TerminalOp<T, Boolean> makeRef(Predicate<? super T> predicate,MatchKind matchKind) {
Objects.requireNonNull(predicate);
Objects.requireNonNull(matchKind);
class MatchSink extends BooleanTerminalSink<T> {
MatchSink() {
super(matchKind);
}

@Override
public void accept(T t) {
if (!stop && predicate.test(t) == matchKind.stopOnPredicateMatches) {
stop = true;
value = matchKind.shortCircuitResult;
}
}
}

return new MatchOp<>(StreamShape.REFERENCE, matchKind, MatchSink::new);
}

经过上面的代码,发现了如下重点部分:

1
2
3
4
5
6
public void accept(T t) {
if (!stop && predicate.test(t) == matchKind.stopOnPredicateMatches) {
stop = true;
value = matchKind.shortCircuitResult;
}
}

已经问过汤师爷,他翻译的结果是:如果还有元素的话,判断一下,如果元素的判断值等于预设值,就不再判断下去,此时中断遍历,叫做短路。

说实话,我说我不理解,汤师爷说,不,你理解,不信你自己用自己的方法来实现这 allMatch,和源码决定一样的处理。我不信,我就这样写了:

1
2
3
4
5
6
7
8
9
10
11
public boolean allMatch(List<Integer> integers, Predicate<Integer> p) {
Objects.requireNonNull(integers);
boolean b = true;
for (Integer integer : integers) {
if (!p.test(integer)) {
b = false;
break;
}
}
return b;
}

汤师爷说,你看吧,你先给了默认值 true,如果有不符合条件的,直接不循环了,返回 false。其他两个你应该也会这样实现的。

allMatch:先默认 true,如果有不满足条件的,短路,返回 false。
anyMatch:先默认 false,如果有满足条件的,短路,返回 true。
noneMatch:先默认 true,如果有满足条件的,短路,返回 false。

JDK 实际就是这么处理的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public void accept(T t) {
if (!stop && predicate.test(t) == matchKind.stopOnPredicateMatches) {
stop = true;
value = matchKind.shortCircuitResult;
}
}

enum MatchKind {
//当 predicate.test == true 时返回并中断 true
//有满足就为 true
ANY(true, true),
//当 predicate.test == false 时返回并中断 false
//有不满足就 false
ALL(false, false),
//当 predicate.test == true 时返回并中断 false
//有满足的就 false
NONE(true, false);

// 终止条件,也就是 predicate.test 的值是这个就终止
private final boolean stopOnPredicateMatches;
// 短路后的结果
private final boolean shortCircuitResult;

private MatchKind(boolean stopOnPredicateMatches, boolean shortCircuitResult) {
this.stopOnPredicateMatches = stopOnPredicateMatches;
this.shortCircuitResult = shortCircuitResult;
}
}

代码里其实已经解释的差不多了,那就只剩一个问题了:在 JDK,初始默认值是什么?

方法默认值短路值
anyMatchfalsetrue
allMatchtruefalse
noneMatchtruefalse

我猜是 !shortCircuitResult,你呢?

源码再次验证猜想:

1
2
3
4
5
6
7
8
9
10
11
12
13
private static abstract class BooleanTerminalSink<T> implements Sink<T> {
boolean stop;
boolean value;
// value 果然是 !shortCircuitResult
BooleanTerminalSink(MatchKind matchKind) {
value = !matchKind.shortCircuitResult;
}
//计算结果时,是用这个方法,也就是说,value是match返回的结果
public boolean getAndClearState() {
return value;
}
...这里省略
}

两个 find 操作

方法定义

Optional<T> findAny()
Optional<T> findFirst()

findAny,返回流内任意一个元素;findFirst,返回流内第一个元素。

这里需要指出的是,返回结果是 Optional,而不是直接的对象。

使用举例

findAny

1
2
3
4
5
6
7
public void findAnyTest() {
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
// 只会输出 1
integers.stream().findAny().ifPresent(System.out::println);
//输出结果不一定是 1,任何一个都有可能
integers.parallelStream().findAny().ifPresent(System.out::println);
}

findAny 返回的是流内的任意一个元素。但是如果是串行流,第一个元素永远最先取到,所以相当于 FindFirst。如果是并行流,哪个元素先取到就不好说了,任何一个元素都有可能被先取到。

findFirst

1
2
3
4
5
6
7
public void findFirstTest() {
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
// 输出只会是1
integers.stream().findFirst().ifPresent(System.out::println);
// 输出也只会是1
integers.parallelStream().findFirst().ifPresent(System.out::println);
}

findFirst,顾名思义,找第一个元素,它不应该因为是并行流或者串行流而有不同。在一个有序流里,第一个元素是哪个是很明确的。

总结

  • 本文讲了 allMatchanyMatchnoneMatchfindFirstfindAny 五种终止操作,对应的含义从方法名上能直接判断出来。
  • 当流内无元素时,allMatch 会返回 true。
  • findAny 在并行流时才会任意返回,在串行流时只会返回第一个。

注:本文配套代码可在 github 查看:stream-and-lambda