引言
通過前面的學習,我們知道了,流可以用類似於數據庫的操作處理集合。它們支持兩種類型的操作:中間操作(如filter或map)和 終端操作(如count、findFirst、forEach和reduce)。
中間操作可以鏈接起來,將一個流轉換爲另一個流。這些操作不會消耗流,其目的是建立一個流水線。與此相反,終端操作會消耗流,以產生一個終結果,例如返回流中的最大元素。
本篇博客主要介紹collect的使用,前面也接觸過它的終端操作,主要是用來把Stream中所有的元素結合成一個List。下面主要是學習它的歸約操作,就像reduce一樣可以接受各種做法作爲參數,將流中的元素累積成一個彙總結果。
Collectors方法
下面先通過一張表格,看一下Collectors類中給我們提供了哪些方法:
方法名稱 | 返回類型 | 描述 |
---|---|---|
toList | List | 把流中所有項目收集到一個 List |
toSet | Set | 把流中所有項目收集到一個 Set,刪除重複項 |
toCollection | Collection | 把流中所有項目收集到給定的供應源創建的集合 |
counting | Long | 計算流中元素的個數 |
summingInt | Integer | 對流中項目的一個整數屬性求和 |
averagingInt | Double | 計算流中項目 Integer 屬性的平均值 |
summarizingInt | IntSummaryStatistics | 收集關於流中項目 Integer 屬性的統計值,例如大、小、 總和與平均值 |
joining | String | 連接對流中每個項目調用 toString 方法所生成的字符串 |
maxBy | Optional | 一個包裹了流中按照給定比較器選出的大元素的 Optional, 或如果流爲空則爲 Optional.empty() |
minBy | Optional | 一個包裹了流中按照給定比較器選出的小元素的 Optional,或如果流爲空則爲 Optional.empty() |
reducing | 歸約操作產生的類型 | 從一個作爲累加器的初始值開始,利用 BinaryOperator 與流 中的元素逐個結合,從而將流歸約爲單個值 |
collectingAndThen | 轉換函數返回的類型 | 包裹另一個收集器,對其結果應用轉換函數 |
groupingBy | Map<K, List> | 根據項目的一個屬性的值對流中的項目作問組,並將屬性值作 爲結果 Map 的鍵 |
partitioningBy | Map<Boolean,List> | 根據對流中每個項目應用謂詞的結果來對項目進行分區 |
代碼實例
下面就通過一些實例,來實踐一下上面的方法:
(1) 計算總數
//counting
long howManyDishes = menu.stream().collect(Collectors.counting());
System.out.println(howManyDishes);
//count
long howManyDishes2 = menu.stream().count();
System.out.println(howManyDishes2);
計算集合的數量,我們可以使用Collectors的counting方法,也可以直接使用stream下的count方法。
(2) 最大值和最小值
//熱量最高和最低
Comparator<Dish> dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories);
Optional<Dish> mostCalorieDish = menu.stream().collect(maxBy(dishCaloriesComparator));
Optional<Dish> lessCalorieDish = menu.stream().collect(minBy(dishCaloriesComparator));
System.out.println(mostCalorieDish);
System.out.println(lessCalorieDish);
maxBy和minBy可分別用來計算集合中的最大值和最小值
(3) 總和
int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));
System.out.println(totalCalories);
summingInt可接受一 個把對象映射爲求和所需int的函數,並返回一個收集器。除了int類型,Collectors.summingLong和Collectors.summingDouble方法的作用完全一樣,可以用於求和字段爲long或double的情況。
(4) 平均值
double avgCalories = menu.stream().collect(averagingInt(Dish::getCalories));
System.out.println(avgCalories);
Collectors.averagingInt,連同對應的averagingLong和 averagingDouble可以計算數值的平均數。
(5) 統計數據
IntSummaryStatistics menuStatistics = menu.stream().collect(summarizingInt(Dish::getCalories));
System.out.println(menuStatistics);
很多時候,我們可能需要總和、平均值等多個結果,我們可以通過一次summarizing操作就可以得到數據的總和、平均值、最大值和最小值。
同樣,相應的summarizingLong和summarizingDouble工廠方法有相關的LongSummary- Statistics和DoubleSummaryStatistics類型,適用於收集的屬性是原始類型long或 double的情況。
(6) 連接字符串
String shortMenu = menu.stream().map(Dish::getName).collect(joining());
System.out.println(shortMenu);
joining工廠方法返回的收集器會把對流中每一個對象應用toString方法得到的所有字符串連接成一個字符串。
上面的結果並不便於查看,所以,我們可以選擇元素之間按指定的符號進行分割:
String shortMenuSplit = menu.stream().map(Dish::getName).collect(joining(", "));
System.out.println(shortMenuSplit);
(7) 分組
- 直接按類型進行分組,返回的結果爲Map,Map的key爲類型,value爲對應的菜餚:
Map<Dish.Type, List<Dish>> dishesByType = menu.stream().collect(groupingBy(Dish::getType));
System.out.println(dishesByType);
- 按熱量,進行分組,返回的結果爲Map,Map的key爲自己定義的類型,value爲對應的菜餚:
Map<CaloricLevel, List<Dish>> dishesByCaloricLevel = menu.stream().collect(
groupingBy(dish -> {
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT; } ));
System.out.println(dishesByCaloricLevel);
- 如果需要同時按類型和熱量分組,怎麼做?使用兩次groupBy就能實現:
Map<Dish.Type, Map<CaloricLevel, List<Dish>>> dishesByTypeCaloricLevel = menu.stream().collect(
groupingBy(Dish::getType,
groupingBy(dish -> {if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;} )) );
System.out.println(dishesByTypeCaloricLevel);
- 在子組中查找數據,先按類型groupBy,再用collectingAndThen查出每類中最大熱量的食物:
Map<Dish.Type, Dish> mostCaloricByType = menu.stream().collect(
groupingBy(Dish::getType,
collectingAndThen(
maxBy(comparingInt(Dish::getCalories)),
Optional::get)));
System.out.println("10:" + mostCaloricByType);
(8) 分區
- 將素食與非素食分開,返回的結果爲Map,Map的key爲是否爲素食的值(true/false),value爲名稱集合
Map<Boolean,List<Dish>> vegetarianDishes = menu.stream().collect(partitioningBy(Dish::isVegetarian));
System.out.println(vegetarianDishes);
若我們需要得到所有素食,可以通過操作Map的結果get(true)得到對應的List:
List<Dish> vegetarianList = vegetarianDishes.get(true);
System.out.println(vegetarianList);
或者我們也可以通過之前用到的filter直接過濾出List結果集:
List<Dish> vegetarianFilter = menu.stream().filter(Dish::isVegetarian).collect(toList());
System.out.println(vegetarianFilter);
- 分區與分組結合使用,先按是否是素食分區,再將結果按類型分組:
Map<Boolean, Map<Dish.Type, List<Dish>>> vegetarianDishesByType = menu.stream().collect(partitioningBy(Dish::isVegetarian,groupingBy(Dish::getType)));
System.out.println("12:" + vegetarianDishesByType);
總結
通過本篇博客的總結學習,和java7的代碼相比,很容易感受到代碼簡化了很多。以上代碼示例,已更新至github,地址:java8Collect學習