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); 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, 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
| public final boolean allMatch(Predicate<? super P_OUT> predicate) { return evaluate(MatchOps.makeRef(predicate, MatchOps.MatchKind.ALL)); }
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 { ANY(true, true), ALL(false, false), NONE(true, false);
private final boolean stopOnPredicateMatches; private final boolean shortCircuitResult;
private MatchKind(boolean stopOnPredicateMatches, boolean shortCircuitResult) { this.stopOnPredicateMatches = stopOnPredicateMatches; this.shortCircuitResult = shortCircuitResult; } }
|
代码里其实已经解释的差不多了,那就只剩一个问题了:在 JDK,初始默认值是什么?
方法 | 默认值 | 短路值 |
---|
anyMatch | false | true |
allMatch | true | false |
noneMatch | true | false |
我猜是 !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; BooleanTerminalSink(MatchKind matchKind) { value = !matchKind.shortCircuitResult; } 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); integers.stream().findAny().ifPresent(System.out::println); 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); integers.stream().findFirst().ifPresent(System.out::println); integers.parallelStream().findFirst().ifPresent(System.out::println); }
|
findFirst
,顾名思义,找第一个元素,它不应该因为是并行流或者串行流而有不同。在一个有序流里,第一个元素是哪个是很明确的。
总结
- 本文讲了
allMatch
、anyMatch
、noneMatch
、findFirst
、findAny
五种终止操作,对应的含义从方法名上能直接判断出来。 - 当流内无元素时,
allMatch
会返回 true。 findAny
在并行流时才会任意返回,在串行流时只会返回第一个。
注:本文配套代码可在 github
查看:stream-and-lambda