介紹 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可以對流元素根據其屬性進行分組,然後進一步收集、改變並收集至最終的容器中。