例子:
怎樣用map和reduce方法數一數流中有多少個菜呢?
答案:要解決這個問題,你可以把流中每個元素都映射成數字1,然后用reduce求和。這相當於按順序數流中的元素個數。
int count = menu.stream() .map(d -> 1) .reduce(0, (a, b) -> a + b);
map和reduce的連接通常稱為map-reduce模式,因Google用它來進行網絡搜索而出名,因為它很容易並行化。同時,內置count方法也可用來計算流中元素的個數:
long count = menu.stream().count();
歸約方法的優勢與並行化 相比於前面寫的逐步迭代求和,使用reduce的好處在於,這里的迭代被內部迭代抽象掉了,這讓內部實現 得以選擇並行執行reduce操作。而迭代式求和例子要更新共享變量sum,這不是那么容易並行化的。如果你 加入了同步,很可能會發現線程競爭抵消了並行本應帶來的性能提升!這種計算的並行化需要另一種辦法: 將輸入分塊,分塊求和,最后再合並起來。但這樣的話代碼看起來就完全不一樣了。以后你會認識到使用 分支/合並框架來做是什么樣子。但現在重要的是要認識到,可變的累加器模式對於並行化來說是死路一條。 你需要一種新的模式,這正是reduce所提供的。使用流來對所有的元素並行求和時,你的代碼幾乎不用修改: stream()換成了parallelStream()。 int sum = numbers.parallelStream().reduce(0, Integer::sum); 但要並行執行這段代碼也要付一定代價:傳遞給reduce的Lambda不能更改狀態(如實例變量),而且操作 必須滿足結合律才可以按任意順序執行。
流操作:無狀態和有狀態 你已經看到了很多的流操作。乍一看流操作簡直是靈丹妙葯,而且只要在從集合生成流的時候把Stream 換成parallelStream就可以實現並行。當然,對於許多應用來說確實是這樣,就像前面的那些例子。你可以 把一張菜單變成流,用filter選出某一類的菜餚,然后對得到的流做map來對卡路里求和,最后reduce得到菜 單的總熱量。這個流計算甚至可以並行進行。但這些操作的特性並不相同。它們需要操作的內部狀態還是有些 問題的。 諸如map或filter等操作會從輸入流中獲取每一個元素,並在輸出流中得到0或1個結果。這些操作一般 都是無狀態的:它們沒有內部狀態(假設用戶提供的Lambda或方法引用沒有內部可變狀態)。 但諸如reduce、sum、max等操作需要內部狀態來累積結果。在上面的情況下,內部狀態很小。在我們的例 子里就是一個int或double。不管流中有多少元素要處理,內部狀態都是有界的。 相反,諸如sort或distinct等操作一開始都和filter和map差不多——都是接受一個流,再生成一個流 (中間操作),但有一個關鍵的區別。從流中排序和刪除重復項時都需要知道先前的歷史。例如,排序要求所有 元素都放入緩沖區后才能給輸出流加入一個項目,這一操作的存儲要求是無界的。要是流比較大或是無限的, 就可能會有問題(把質數流倒序會做什么呢?它應當返回最大的質數,但數學告訴我們它不存在)。我們 把這些操作叫作有狀態操作。
操作 | 類型 | 返回類型 | 使用的類型/函數式接口 | 函數描述符 |
filter | 中間 | Stream<T> | Predicate<T> | T -> boolean |
distinct | 中間(有狀態-無界) | Stream<T> | ||
skip | 中間(有狀態-有界) | Stream<T> | long | |
limit | 中間 | Strem<T> | long | |
map | 中間 | Stream<R> | Function<T,R> | T -> R |
flatMap | 中間(有狀態-無界) | Stream<R> | Function<T,Stream<R>> | T -> Stream<R> |
sorted | 終端 | Stream<T> | Comparator<T> | (T, T) -> int |
anyMatch | 終端 | boolean | Predicate<T> | T -> boolean |
noneMatch | 終端 | boolean | Predicate<T> | T -> boolean |
allMatch | 終端 | boolean | Predicate<T> | T -> boolean |
findAny | 終端 | Optional<T> | ||
findFirst | 終端 | Optional<T> | ||
forEach | 終端 | void | Consumer<T> | T -> void |
collect | 終端 | R | Collector<T,A,R> | |
reduce | 終端(有狀態-有界) | Optional<T> | BinaryOperator<T> | (T, T) -> T |
count | 終端 | long |