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
的陷阱才可以。
看起來是不是有些熟悉?沒錯,是有點像 &&
、 ||
和 !
的條件表示式。
使用舉例
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 裡沒有任何元素呢?會不會報錯?執行結果又如果呢?
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 了!!!
一探原始碼查究竟
// 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); }
經過上面的程式碼,發現瞭如下重點部分:
public void accept(T t) { if (!stop && predicate.test(t) == matchKind.stopOnPredicateMatches) { stop = true; value = matchKind.shortCircuitResult; } }
已經問過湯師爺,他翻譯的結果是:如果還有元素的話,判斷一下,如果元素的判斷值等於預設值,就不再判斷下去,此時中斷遍歷,叫做短路。
說實話,我說我不理解,湯師爺說,不,你理解,不信你自己用自己的方法來實現這 allMatch
,和原始碼決定一樣的處理。我不信,我就這樣寫了:
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 實際就是這麼處理的。
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,初始預設值是什麼?
方法 | 預設值 | 短路值 |
---|---|---|
anyMatch | false | true |
allMatch | true | false |
noneMatch | true | false |
我猜是 !shortCircuitResult
,你呢?
原始碼再次驗證猜想:
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
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
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
,顧名思義,找第一個元素,它不應該因為是並行流或者序列流而有不同。在一個有序流裡,第一個元素是哪個是很明確的。
總結
-
本文講了
allMatch
、anyMatch
、noneMatch
、findFirst
、findAny
五種終止操作,對應的含義從方法名上能直接判斷出來。 -
當流內無元素時,
allMatch
會返回 true。 -
findAny
在並行流時才會任意返回,在序列流時只會返回第一個。
注:本文配套程式碼可在 github
檢視: stream-and-lambda
- stream and lambda(18) - 終止操作之 stream 收集器 collectors
- stream and lambda(15) - 終止操作之 stream 陣列操作 toArray
- stream and lambda(13) - 終止操作之計數 count 與比較 min、max
- stream and lambda(12) - 終止操作之查詢與匹配(findAny、findFirst、allMatch、anyMatch、noneMatch)
- stream and lambda(11) - Optional 簡介
- stream and lambda(10) - 中間操作之排序sorted與除錯peek
- stream and lambda(9) - 中間操作之map操作(map、flatmap)
- stream and lambda(8) - 中間操作之篩選操作(filter、distinct、limit、skip)
- stream and lambda(7) - stream 的建立
- stream and lambda(6) - stream 簡介
- stream and lambda(5) - lambda 表示式最佳實踐
- stream and lambda(3) - jdk 提供的函式式介面
- stream and lambda(2) - 函式式介面簡介
- stream and lambda(1) - lambda 簡介