介紹 Java 8 groupingBy Collector

介紹 Java 8 groupingBy Collector

本文我們探討下Java 8 groupingBy Collector,通過不同的示例進行詳細講解。

GroupingBy Collector

Java 8 Stream API 提供了聲明方式處理流數據。static工廠方法Collectors.groupingBy() 和 Collectors.groupingByConcurrent() 實現類似SQL語句的“Group By”字句功能,實現根據一些屬性進行分組並把結果存在Map實例。

重載groupingBy的幾個方法:

static <T,K> Collector<T,?,Map<K,List<T>>> 
  groupingBy(Function<? super T,? extends K> classifier)

提供後續收集器參數:

static <T,K,A,D> Collector<T,?,Map<K,D>>
  groupingBy(Function<? super T,? extends K> classifier, 
    Collector<? super T,A,D> downstream)

提供map 提供者和後續收集器參數:

static <T,K,D,A,M extends Map<K,D>> Collector<T,?,M>
  groupingBy(Function<? super T,? extends K> classifier, 
    Supplier<M> mapFactory, Collector<? super T,A,D> downstream)

下面通過示例詳細看看每個方法的應用場景。

基礎準備

爲了演示groupingBy(),讓我們定義BlogPost類(作爲流使用):

class BlogPost {
    String title;
    String author;
    BlogPostType type;
    int likes;
}

BlogPostType:

enum BlogPostType {
    NEWS,
    REVIEW,
    GUIDE
}

定義一個BlogPost列表:

List<BlogPost> posts = Arrays.asList( ... );

同時也訂一個Tuple類用於根據多個屬性進行分組:

class Tuple {
    BlogPostType type;
    String author;
}

通過單個屬性簡單分組

首先從最簡單的groupingBy 方法開始,使用一個分類函數作爲參數。分類函數應用於流中的每個元素。函數的返回值用於map的key,映射至分組集合。根據post類型進行分組的代碼:

Map<BlogPostType, List<BlogPost>> postsPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType));

根據複雜類型進行分組

Map<Tuple, List<BlogPost>> postsPerTypeAndAuthor = posts.stream()
  .collect(groupingBy(post -> new Tuple(post.getType(), post.getAuthor())));

分組類型不限於標量或字符串類型。map的key可以是任何對象類型,只有我們確保實現必要的equals 和 hashcode 方法。下面根據組合類型進行分組:

Map<Tuple, List<BlogPost>> postsPerTypeAndAuthor = posts.stream()
  .collect(groupingBy(post -> new Tuple(post.getType(), post.getAuthor())));

修改返回Map值類型

第二個重載groupingBy方法帶另一個參數指定後續收集器,應用於第一個集合結果。當我們僅指定一個分類器函數,沒有後續收集器,則返回toList()集合。如何後續收集器使用toSet(),則會獲得Set集合,而不是List:

Map<BlogPostType, Set<BlogPost>> postsPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType, toSet()));

根據多個字段進行分組

與應用後續收集器不同的是,可以指定第二個分類器對第一個分組結果再分組。對BlogPost類的List根據作者和類型進行分組代碼如下:

Map<String, Map<BlogPostType, List>> map = posts.stream()
  .collect(groupingBy(BlogPost::getAuthor, groupingBy(BlogPost::getType)));

獲取分組結果的平均值

通過使用後續收集器可以對分組函數的結果使用聚集函數。獲取每種blog類型的平均數:

Map<BlogPostType, Double> averageLikesPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType, averagingInt(BlogPost::getLikes)));

獲取分組結果之和

計算每個分組之和:

Map<BlogPostType, Integer> likesPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType, summingInt(BlogPost::getLikes)));

獲取分組結果的最大值和最小值

另外可以通過聚集函數獲取最大數量的blog類型:

Map<BlogPostType, Optional<BlogPost>> maxLikesPerPostType = posts.stream()
  .collect(groupingBy(BlogPost::getType,
  maxBy(comparingInt(BlogPost::getLikes))));

類似的,我們能應用minBy後續收集器獲得最小數量的分類。注意,maxBy和minBy 收集器考慮到應用它的集合可能是空的。這就是爲什麼map中的值類型是可選的。

獲取屬性分組結果的摘要信息

Collectors API提供了摘要收集器,用於需要同時獲取數值屬性的count,sum, minimum, maximum ,average 值。下面計算每種類型的摘要信息:

Map<BlogPostType, IntSummaryStatistics> likeStatisticsPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType, 
  summarizingInt(BlogPost::getLikes)));

每個類型的IntSummaryStatistics 對象包括屬性的 count, sum, average, min 和 max值。另外摘要對象也有double和long類型。

映射分組結果至不同類型

更復雜的聚集可以對分類結果應用後續映射收集器。下面獲得每個類型的連接blog的標題。

Map<BlogPostType, String> postsPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType, 
  mapping(BlogPost::getTitle, joining(", ", "Post titles: [", "]"))));

上面代碼實現映射每個BlogPost 實例至title,然後reduce 文章標題流至連接字符串。本例Map的值是字符串,而不是默認List類型。

修改返回 Map 類型

當使用groupingBy 收集器,我們不能確定返回Map的類型。如果我們想指定特定Map類型作爲返回值,我們使用三個參數的groupingBy 方法,通過提供Map supplier函數,其允許我們改變Map的類型。

通過EnumMap supplier函數給goupingBy方法獲取EnumMap:

EnumMap<BlogPostType, List<BlogPost>> postsPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType, 
  () -> new EnumMap<>(BlogPostType.class), toList()));

併發Grouping By Collector

類似於 groupingBy, 也有groupingByConcurrent 收集器,其利用多線程架構。其有三個重載方法,帶有與groupingBy一樣的參數。然而,groupingByConcurrent 收集器的返回值必須是ConcurrentHashMap 類或其子類。

實現併發分組的代碼如下:

ConcurrentMap<BlogPostType, List<BlogPost>> postsPerType = posts.parallelStream()
  .collect(groupingByConcurrent(BlogPost::getType));

如果你選擇傳遞Map supplier函數給groupingByConcurrent 收集器,那麼需要確保函數返回值也必須是ConcurrentHashMap 類或其子類。

總結

本文我們看了Java 8 中提供的幾個groupingBy collector示例。groupingBy可以對流元素根據其屬性進行分組,然後進一步收集、改變並收集至最終的容器中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章