collect() 接收一個類型爲 Collector 的參數,這個參數決定了如何把流中的元素聚合到其它數據結構中。Collectors 類包含了大量經常使用收集器的工廠方法,toList() 和 toSet() 就是其中最多見的兩個,除了它們還有不少收集器,用來對數據進行對複雜的轉換。算法
指令式代碼和函數式對比:安全
要是作多級分組,指令式和函數式之間的區別就會更加明顯:因爲須要好多層嵌套循環和條件,指令式代碼很快就變得更難閱讀、更難維護、更難修改。相比之下,函數式版本只要再加上 一個收集器就能夠輕鬆地加強數據結構
預約義收集器,也就是那些能夠從Collectors類提供的工廠方法(例如groupingBy)建立的收集器。它們主要提供了三大功能:app
在須要將流項目重組成集合時,通常會使用收集器(Stream方法collect 的參數)。再寬泛一點來講,但凡要把流中全部的項目合併成一個結果時就能夠用。這個結果能夠是任何類型,能夠複雜如表明一棵樹的多級映射,或是簡單如一個整數。分佈式
Collectors.maxBy和 Collectors.minBy,來計算流中的最大或最小值。這兩個收集器接收一個Comparator參數來比較流中的元素。你能夠建立一個Comparator來根據所含熱量對菜餚進行比較:函數
System.out.println("找出熱量最高的食物:");
Optional<Dish> collect = DataUtil.genMenu().stream().collect(Collectors.maxBy(Comparator.comparingInt(Dish::getCalories)));
collect.ifPresent(System.out::println);
System.out.println("找出熱量最低的食物:");
Optional<Dish> collect1 = DataUtil.genMenu().stream().collect(Collectors.minBy(Comparator.comparingInt(Dish::getCalories)));
collect1.ifPresent(System.out::println);複製代碼
Collectors類專門爲彙總提供了一個工廠方法:Collectors.summingInt。它可接受一個把對象映射爲求和所需int的函數,並返回一個收集器;該收集器在傳遞給普通的collect方法後即執行咱們須要的彙總操做。舉個例子來講,你能夠這樣求出菜單列表的總熱量:優化
Integer collect = DataUtil.genMenu().stream().collect(Collectors.summingInt(Dish::getCalories));
System.out.println("總熱量:" + collect);
Double collect1 = Arrays.asList(0.1, 0.2, 0.3).stream().collect(Collectors.summingDouble(Double::doubleValue));
System.out.println("double和:" + collect1);
Long collect2 = Arrays.asList(1L, 2L, 3L).stream().collect(Collectors.summingLong(Long::longValue));
System.out.println("long和:" + collect2);複製代碼
Collectors.averagingInt,averagingLong和averagingDouble能夠計算數值的平均數:ui
Double collect = DataUtil.genMenu().stream().collect(Collectors.averagingInt(Dish::getCalories));
System.out.println("平均熱量:" + collect);
Double collect1 = Arrays.asList(0.1, 0.2, 0.3).stream().collect(Collectors.averagingDouble(Double::doubleValue));
System.out.println("double 平均值:" + collect1);
Double collect2 = Arrays.asList(1L, 2L, 3L).stream().collect(Collectors.averagingLong(Long::longValue));
System.out.println("long 平均值:" + collect2);複製代碼
你可能想要獲得兩個或更多這樣的結果,並且你但願只需一次操做就能夠完成。在這種狀況下,你可使用summarizingInt工廠方法返回的收集器。例如,經過一次summarizing操做你能夠就數出菜單中元素的個數,並獲得熱量總和、平均值、最大值和最小值:spa
IntSummaryStatistics collect = DataUtil.genMenu().stream().collect(Collectors.summarizingInt(Dish::getCalories));
System.out.println("int:" + collect);
DoubleSummaryStatistics collect1 = Arrays.asList(0.1, 0.2, 0.3).stream().collect(Collectors.summarizingDouble(Double::doubleValue));
System.out.println("double:" + collect1);
LongSummaryStatistics collect2 = Arrays.asList(1L, 2L, 3L).stream().collect(Collectors.summarizingLong(Long::longValue));
System.out.println("long:" + collect2);複製代碼
joining工廠方法返回的收集器會把對流中每個對象應用toString方法獲得的全部字符串鏈接成一個字符串。線程
String collect = DataUtil.genMenu().stream().map(Dish::getName).collect(Collectors.joining());複製代碼
請注意,joining在內部使用了StringBuilder來把生成的字符串逐個追加起來。幸虧,joining工廠方法有一個重載版本能夠接受元素之間的分界符,這樣你就能夠獲得一個都好分隔的名稱列表:
String collect1 = DataUtil.genMenu().stream().map(Dish::getName).collect(Collectors.joining(","));複製代碼
全部收集器,都是一個能夠用reducing工廠方法定義的歸約過程的特殊狀況而已。Collectors.reducing工廠方法是全部這些特殊狀況的通常化。它須要三個參數:
下面兩個是相同的操做:
Optional<Dish> collect = DataUtil.genMenu().stream().collect(Collectors.maxBy(Comparator.comparingInt(Dish::getCalories)));
Optional<Dish> mostCalorieDish = menu.stream().collect(reducing((d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2));複製代碼
用Collectors.groupingBy工廠方法返回的收集器就能夠輕鬆地完成任務:
Map<Dish.Type, List<Dish>> collect = DataUtil.genMenu().stream().collect(Collectors.groupingBy(Dish::getType));複製代碼
給groupingBy方法傳遞了一個Function(以方法引用的形式),它提取了流中每 一道Dish的Dish.Type。咱們把這個Function叫做分類函數,由於它用來把流中的元素分紅不一樣的組。分組操做的結果是一個Map,把分組函數返回的值做爲映射的鍵,把流中全部具備這個分類值的項目的列表做爲對應的映射值。
要實現多級分組,咱們可使用一個由雙參數版本的Collectors.groupingBy工廠方法建立的收集器,它除了普通的分類函數以外,還能夠接受collector類型的第二個參數。那麼要進行二級分組的話,咱們能夠把一個內層groupingBy傳遞給外層groupingBy,並定義一個爲流中項目分類的二級標準:
Map<Dish.Type, Map<CaloricLevel, List<Dish>>> collect1 = DataUtil.genMenu().stream().collect(
Collectors.groupingBy(Dish::getType,
Collectors.groupingBy(dish -> {
if (dish.getCalories() <= 400) {
return CaloricLevel.DIET;
} else if (dish.getCalories() <= 700) {
return CaloricLevel.NORMAL;
} else return CaloricLevel.FAT;
}))
);複製代碼
傳遞給第一個groupingBy的第二個收集器能夠是任何類型,而不必定是另外一個groupingBy。例如,要數一數菜單中每類菜有多少個,能夠傳遞counting收集器做爲groupingBy收集器的第二個參數:
Map<Dish.Type, Long> collect2 = DataUtil.genMenu().stream().collect(Collectors.groupingBy(Dish::getType, Collectors.counting()));複製代碼
還要注意,普通的單參數groupingBy(f)(其中f是分類函數)其實是groupingBy(f, toList())的簡便寫法。把收集器返回的結果轉換爲另外一種類型,你可使用 Collectors.collectingAndThen工廠方法返回的收集器,接受兩個參數:要轉換的收集器以及轉換函數,並返回另外一個收集器。
Map<Dish.Type, Dish> collect3 = DataUtil.genMenu().stream().collect(Collectors.groupingBy(Dish::getType,
Collectors.collectingAndThen(
Collectors.maxBy(Comparator.comparingInt(Dish::getCalories)),
Optional::get
)));複製代碼
這個操做放在這裏是安全的,由於reducing收集器永遠都不會返回Optional.empty()。
經常和groupingBy聯合使用的另外一個收集器是mapping方法生成的。這個方法接受兩個參數:一個函數對流中的元素作變換,另外一個則將變換的結果對象收起來。其目的是在累加以前對每一個輸入元素應用一個映射函數,這樣就可讓接受特定類型元素的收器適應不一樣類型的對象。咱們來看一個使用這個收集器的實際例子。比方說你想要知道,對於每種類型的Dish, 菜單中都有哪些CaloricLevel。
Map<Dish.Type, Set<CaloricLevel>> collect4 = DataUtil.genMenu().stream().collect(Collectors.groupingBy(
Dish::getType, Collectors.mapping(
dish -> {
if (dish.getCalories() <= 400) {
return CaloricLevel.DIET;
} else if (dish.getCalories() <= 700) {
return CaloricLevel.NORMAL;
} else return CaloricLevel.FAT;
}, Collectors.toSet()
)
));複製代碼
分區是分組的特殊狀況:由一個謂詞(返回一個布爾值的函數)做爲分類函數,它稱分類函數。分區函數返回一個布爾值,這意味着獲得的分組Map的鍵類型是Boolean,因而它最多能夠 分爲兩組——true是一組,false是一組。例如,若是想要把菜按照素食和非素食分開:
Map<Boolean, List<Dish>> collect = DataUtil.genMenu().stream().collect(Collectors.partitioningBy(Dish::isVegetarian));
System.out.println(collect.get(true));
partitioningBy 工廠方法有一個重載版本,能夠像下面這樣傳遞第二個收集器:
Map<Boolean, Map<Dish.Type, List<Dish>>> collect1 = DataUtil.genMenu().stream().collect(Collectors.partitioningBy(
Dish::isVegetarian, Collectors.groupingBy(Dish::getType)
));複製代碼
分區看做分組一種特殊狀況。
public interface Collector<T, A, R> {
Supplier<A> supplier();
BiConsumer<A, T> accumulator();
Function<A, R> finisher();
BinaryOperator<A> combiner();
Set<Characteristics> characteristics();
}複製代碼
本列表適用如下定義:
supplier方法必須返回一個結果爲空的Supplier,也就是一個無參數函數,在調用時它會建立一個空的累加器實例,供數據收集過程使用。
accumulator方法會返回執行歸約操做的函數。當遍歷到流中第n個元素時,這個函數執行時會有兩個參數:保存歸約結果的累加器(已收集了流中的前n-1個項目),還有第n個元素自己。該函數將返回void,由於累加器是原位更新,即函數的執行改變了它的內部狀態以體現遍歷的元素的效果。
在遍歷完流後,finisher方法必須返回在累積過程的最後要調用的一個函數,以便將累加器對象轉換爲整個集合操做的最終結果。順序歸約過程的邏輯步驟:
四個方法中的最後一個——combiner方法會返回一個供歸約操做使用的函數,它定義了對流的各個子部分進行並行處理時,各個子部分歸約所得的累加器要如何合併:
最後一個方法——characteristics會返回一個不可變的Characteristics集合,它定義了收集器的行爲——尤爲是關於流是否能夠並行歸約,以及可使用哪些優化的提示。Characteristics是一個包含三個項目的枚舉。