【Java8實戰】用流收集數據

引言

通過前面的學習,我們知道了,流可以用類似於數據庫的操作處理集合。它們支持兩種類型的操作:中間操作(如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學習

發佈了343 篇原創文章 · 獲贊 240 · 訪問量 61萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章