Java 8分組指南By Collector
1.簡介
在本教程中,我們將使用各種示例來了解groupingBy
收集器的工作方式。
為了使我們理解本教程介紹的內容,我們需要Java 8功能的基礎知識。我們可以看一下Java 8 Streams的介紹和Java 8 Streams的指南,以了解這些基礎知識。
2.Collector.groupingBy
Java 8 Stream
API使我們能夠以聲明的方式處理數據集合。
靜態工廠方法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)
2.1。示例代碼設置
為了演示groupingBy()
的用法,讓我們定義一個BlogPost
類(我們將使用BlogPost
對象流):
class BlogPost {
String title;
String author;
BlogPostType type;
int likes;
}
接下來, BlogPostType
:
enum BlogPostType {
NEWS,
REVIEW,
GUIDE
}
然後是BlogPost
對象的List
:
List<BlogPost> posts = Arrays.asList( ... );
我們還定義一個Tuple
類,該類將用於通過組合其type
和author
屬性來對帖子進行分組:
class Tuple {
BlogPostType type;
String author;
}
2.2。單列簡單分組
讓我們從最簡單的groupingBy
方法開始,該方法僅將分類函數作為其參數。分類函數應用於流的每個元素。我們使用函數返回的值作為從groupingBy
收集器獲取的映射的鍵。
要將博客文章列表中的博客文章按type
分組:
Map<BlogPostType, List<BlogPost>> postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType));
2.3。通過複雜Map
鍵類型進行groupingBy
分類函數不限於僅返回標量或String值。只要我們確保實現必要的equals
和hashcode
方法,結果映射的鍵就可以是任何對象。
要將列表中的博客文章按type
和author
在Tuple
實例中組合)進行分組:
Map<Tuple, List<BlogPost>> postsPerTypeAndAuthor = posts.stream()
.collect(groupingBy(post -> new Tuple(post.getType(), post.getAuthor())));
2.4。修改返回的Map
值類型
groupingBy
的第二次重載使用了一個附加的第二收集器(下游收集器),該收集器應用於第一收集器的結果。
當我們指定分類函數但不指定下游收集器時,將在toList()
使用toList()
收集器。
讓我們使用toSet()
收集器作為下游收集器,並獲取一Set
博客文章(而不是List
):
Map<BlogPostType, Set<BlogPost>> postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType, toSet()));
2.5。按多個字段分組
下游收集器的另一種應用是對第一個groupingBy
結果進行次groupingBy
。
首先按author
然後按type
分組BlogPost
的List
:
Map<String, Map<BlogPostType, List>> map = posts.stream()
.collect(groupingBy(BlogPost::getAuthor, groupingBy(BlogPost::getType)));
2.6。從分組結果中獲取平均值
通過使用下游收集器,我們可以將聚合函數應用到分類函數的結果中。
例如,要查找每種博客文章type
的平均點likes
次數:
Map<BlogPostType, Double> averageLikesPerType = posts.stream()
.collect(groupingBy(BlogPost::getType, averagingInt(BlogPost::getLikes)));
2.7。從分組結果中獲取總和
計算每種type
的likes
總數:
Map<BlogPostType, Integer> likesPerType = posts.stream()
.collect(groupingBy(BlogPost::getType, summingInt(BlogPost::getLikes)));
2.8。從分組結果中獲取最大值或最小值
我們可以執行的另一種匯總方式是獲得具有最多“頂”次數的博客帖子:
Map<BlogPostType, Optional<BlogPost>> maxLikesPerPostType = posts.stream()
.collect(groupingBy(BlogPost::getType,
maxBy(comparingInt(BlogPost::getLikes))));
同樣,我們可以運用minBy
下游收集得到的博客文章與最小號likes
。
請注意, maxBy
和minBy
收集器考慮了應用它們的集合可能為空的可能性。這就是為什麼映射中的值類型為Optional<BlogPost>
。
2.9。獲取分組結果屬性的摘要
Collectors
API提供了一個匯總收集器,我們可以在需要同時計算數值屬性的計數,總和,最小值,最大值和平均值的情況下使用。
讓我們為每種不同類型的博客文章的likes屬性計算一個摘要:
Map<BlogPostType, IntSummaryStatistics> likeStatisticsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType,
summarizingInt(BlogPost::getLikes)));
每種類型的IntSummaryStatistics
對象包含likes
屬性的計數,總和,平均值,最小值和最大值。對於雙精度值和長整型值,存在其他匯總對象。
2.10。將分組結果映射到其他類型
通過將mapping
下游收集器應用於分類函數的結果,我們可以實現更複雜的聚合。
讓我們串聯每種博客文章type
的文章title
:
Map<BlogPostType, String> postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType,
mapping(BlogPost::getTitle, joining(", ", "Post titles: [", "]"))));
我們在這裡所做的是將每個BlogPost
實例映射到其title
,然後將帖子標題流減少為一個串聯的String
。在此示例中, Map
值的類型也與默認的List
類型不同。
2.11。修改返回Map
類型
使用groupingBy
收集器時,我們無法對返回的Map
的類型進行假設。如果要具體確定要從分組中獲取哪種類型的Map
,則可以使用groupingBy
方法的第三個變體,該方法允許我們通過傳遞Map
供應商函數來更改Map
的類型。
讓我們通過將EnumMap
函數傳遞給groupingBy
方法來檢索EnumMap
:
EnumMap<BlogPostType, List<BlogPost>> postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType,
() -> new EnumMap<>(BlogPostType.class), toList()));
3.收集器Collector並發groupingBy
與groupingBy
類似的是groupingByConcurrent
收集器,該收集器利用了多核體系結構。該收集器具有三個重載方法,它們採用與groupingBy
收集器的各個重載方法完全相同的參數。但是, groupingByConcurrent
收集器的返回類型必須是ConcurrentHashMap
類的實例或其子類。
要同時執行分組操作,流必須是並行的:
ConcurrentMap<BlogPostType, List<BlogPost>> postsPerType = posts.parallelStream()
.collect(groupingByConcurrent(BlogPost::getType));
如果我們選擇將Map
供應商函數傳遞給groupingByConcurrent
收集器,那麼我們需要確保該函數返回ConcurrentHashMap
或其子類。
4. Java 9新增功能
Java 9引入了兩個新的收集器,它們與groupingBy
配合使用;有關它們的更多信息,請參見此處。
5.結論
在本文中,我們探討了Java 8 Collectors
API提供的groupingBy
收集器的用法。
我們了解瞭如何使用groupingBy
來基於元素的屬性之一對元素流進行分類,以及如何進一步分類,分類和簡化此分類的結果以使其成為最終容器。
本文示例的完整實現可在GitHub項目中找到。