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類,該類將用於通過組合其typeauthor屬性來對帖子進行分組:

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值。只要我們確保實現必要的equalshashcode方法,結果映射的鍵就可以是任何對象。

要將列表中的博客文章按typeauthorTuple實例中組合)進行分組:

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分組BlogPostList

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。從分組結果中獲取總和

計算每種typelikes總數:

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

請注意, maxByminBy收集器考慮了應用它們的集合可能為空的可能性。這就是為什麼映射中的值類型為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項目中找到。