## 1 介紹
本文將展示groupingBy收集器的多個示例。
閱讀本文需要先準備Java Stream和Java收集器Collector的知識。
## 2 GroupingBy收集器
Java8的Stream API允許我們以聲明的方式來處理數據集合。
靜態工廠方法:Collectors.groupingBy(),以及Collectors.groupingByConcunrrent(),給我們提供了類似SQL語句中的"GROUP BY"的功能。這兩個方法將數據按某些屬性分組,並存儲在Map中返回。
下面是幾個重載的groupnigBy方法:
- 參數:分類函數
```
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 準備
先定義一個BlogPost類:
```
class BlogPost {
String title;
String author;
BlogPostType type;
int likes;
}
```
BlogPostType:
```
enum BlogPostType {
NEWS,
REVIEW,
GUIDE
}
```
BlogPost列表:
```
List<BlogPost> posts = Arrays.asList( ... );
```
### 2.2 根據單一欄位分組
最簡單的groupingBy方法,只有一個分類函數做參數。分類函數作用於strema裡面的每個元素。分類函數處理後返回的每個元素作為返回Map的key。
根據博客文章類型來分組:
```
Map<BlogPostType, List<BlogPost>> postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType));
```
### 2.3 根據Map的key的類型分組
分類函數並沒有限制返回字符串或標量值。返回map的key可以是任何對象。只要實現了其equals和hashcode方法。
下面示例根據type和author組合而成的Tuple實例來排序:
```
Map<Tuple, List<BlogPost>> postsPerTypeAndAuthor = posts.stream()
.collect(groupingBy(post -> new Tuple(post.getType(), post.getAuthor())));
```
### 2.4 修改返回Map的value的類型
groupingBy的第二個重載方法有一個額外的collector參數(downstream),此參數作用於第一個collector產生的結果。
如果只用一個分類函數做參數,那麼默認會使用toList()這個collector來轉換結果。
下面的代碼顯示地使用了toSet()這個collector傳遞給downstream這個參數,因此會得到一個博客文章的Set。
```
Map<BlogPostType, Set<BlogPost>> postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType, toSet()));
```
### 2.5 根據多個欄位分組
downstream參數的另外一個用處就是基於分組結果,做第二次分組。
下面代碼,首先根據author分組,然後再根據type分組:
```
Map<String, Map<BlogPostType, List>> map = posts.stream()
.collect(groupingBy(BlogPost::getAuthor, groupingBy(BlogPost::getType)));
```
### 2.6 得到分組結果的平均值
通過使用downstream,我們可以把集合函數應用到第一次分組的結果上。比如,獲取到每種類型博客的被喜歡次數(likes)的平均值:
```
Map<BlogPostType, Double> averageLikesPerType = posts.stream()
.collect(groupingBy(BlogPost::getType, averagingInt(BlogPost::getLikes)));
```
### 2.7 得到分組結果的總計
計算每種類型被喜歡次數的總數:
```
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))));
```
類似的,可以用minxBy得到每種類型博客中被喜歡次數最少的次數是多少。
注意:maxBy和minBy都考慮了當第一次分組得到的結果是空的場景,因此其返回結果(Map的value)是Optional\<BlogPost>。
### 2.9 得到分組結果中某個屬性的統計
Collectors API提供了一個統計collector,可以用來同時計算數量、總計、最小值、最大值、平均值等。
下面來統計一下不同類型博客的被喜歡(likes)這個屬性:
```
Map<BlogPostType, IntSummaryStatistics> likeStatisticsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType,
summarizingInt(BlogPost::getLikes)));
```
返回Map中的value,IntSummaryStatistics對象,包括了每個BlogPostType的文章次數、被喜歡總計、平均值、最大值、最小值。
### 2.10 把分組結果映射為另外的類型
更複雜的聚合操作可以通過應用一個映射downstream收集器到分類函數結果上來實現。
下面代碼講每類博客類型的標題連接起來了。
```
Map<BlogPostType, String> postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType,
mapping(BlogPost::getTitle, joining(", ", "Post titles: [", "]"))));
```
上面的代碼,講每個BlogPost實例映射為了其對應的標題,然後把博客標題的stream連接成了成了字符串,形如「Post titles:[標題1,標題2,標題3]」。
### 2.11 修改返回Map的類型
使用groupingBy的時候,如果我們要指定返回Map的具體類型,可以用第三個重載方法。通過傳入一個Map供應者函數。
下面代碼傳入了一個EnumMap供應者函數,得到返回Map為EnumMap類型。
```
EnumMap<BlogPostType, List<BlogPost>> postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType,
() -> new EnumMap<>(BlogPostType.class), toList()));
```
## 3 並發的分組Collector
類似groupingBy,存在一個groupingByConcurrent收集器,可以利用到多核架構的能力。groupingByConcurrent也有3個重載的方法,與groupingBy類似。
但返回值必須是ConconcurrentHashMap或其子類。
要並發操作分組,那麼stream也必須是並行的:
```
ConcurrentMap<BlogPostType, List<BlogPost>> postsPerType = posts.parallelStream()
.collect(groupingByConcurrent(BlogPost::getType));
```
注意:如果要提供一個Map供應者函數,必須保證函數返回的是ConconcurrentHashMap或其子類。
## 4 Java 9新功能
java9引入兩個新的收集器可以在goupingBy中使用的:[更多詳情]( baeldung.com/java9-stream-collectors)。
## 5 小結
本文討論了Java 8 Collectors API中的groupingBy收集器的幾個例子。
討論了goupingBy如何對stream中的元素基於某個屬性進行分組,以及如何返回結果。
示例代碼見[github]( github.com搜索 eugenp)
下面的tutorials/tree/master/core-java-modules/core-java-8
> [編譯]( baeldung.com啥玩兒意java-groupingby-collector )