黑客日教程-Java8新功能:將數據集合分組,類似SQL的GROUP BY

2021-01-07 黑客日

## 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 )

相關焦點

  • java中有沒有類似sql的group by的功能呢
    我們現在做的很多系統都是離不開資料庫的,所以經常會使用到sql語句做數據增刪改查,而其中查詢使用的應該也是最多的。在sql查詢中有一個分組查詢的功能,就是通過「group by」將數據進行分組處理。那java中有沒有類似這樣的分組功能呢。
  • Oracle分組查詢group by的用法及講解
    group by是sql中比較強大的功能,是在對數據分組統計時必不可少的用法。但是,對於很多經驗不足的同學,經常會寫錯。今天我們就以Oracle為例,來講解下分組查詢group by的用法。再查看數據:SELECT * FROM STUDENT;我們使用group by將這些數據按照性別進行分組:SELECT * FROM STUDENT GROUP BY SSEX;不幸的是,執行失敗了,提示:不是 GROUP BY 表達式!
  • 數據分析利器 pandas 系列教程(四):對比 sql 學 pandas
    作為 pandas
  • 黑客日教程-對比集合的STREAM().FOREACH()和FOREACH()
    1.簡介在java中有多種方式對集合進行遍歷。本教程中將看兩個類似的方法 Collection.stream().forEach()和Collection.forEach()。在大多數情況下,兩者都會產生相同的結果,但是,我們會看到一些微妙的差異。
  • 為什麼我的 GROUP BY 分組又報錯了?
    =only_full_group_by提示信息:SELECT 列表中的第二個表達式(cname)不在 GROUP BY 的子句中,同時它也不是聚合函數;這與 sql 模式:ONLY_FULL_GROUP_BY 不相容。
  • 分組查詢時,select的欄位是否一定要都在group by中?
    分組查詢關鍵字group by通常和集合函數(MAX、MIN、COUNT、SUM、AVG)一起使用,它可以對一列或者多列結果集進行分組。
  • sql 分組語句group by
    聚合函數如sum(),max()等,通常需要結合分組語句group by 使用GROUP BY 語句用於結合合計函數,根據一個或多個列對結果集進行分組
  • Java從零開始學 - 第64篇:分組查詢詳解(group by & having)
    本篇內容 分組查詢語法聚合函數單欄位分組多欄位分組分組前篩選數據分組後篩選數據where和having的區別分組後排序where & group by & having & order by & limit 一起協作mysql分組中的坑
  • SQL Server 動態行轉列(參數化表名、分組列、行轉列欄位、欄位值)
    今天跟大家分享SQL Server 動態行轉列(參數化表名、分組列、行轉列欄位、欄位值)的知識。['+@groupColumn+']'EXEC (@sql_str)(圖5)(七) 在實際的運用中,我經常遇到需要對基礎表的數據進行篩選後再進行行轉列,那麼下面的腳本將滿足你這個需求,效果如圖6所示:
  • 神奇的 SQL,Group By 真扎心,原來是這樣!
    =only_full_group_by提示信息:SELECT 列表中的第二個表達式(cname)不在 GROUP BY 的子句中,同時它也不是聚合函數;這與 sql 模式:ONLY_FULL_GROUP_BY 不相容。
  • 神奇的 SQL,GROUP BY 真扎心,原來是這樣!
    =only_full_group_by提示信息:SELECT 列表中的第二個表達式(cname)不在 GROUP BY 的子句中,同時它也不是聚合函數;這與 sql 模式:ONLY_FULL_GROUP_BY 不相容。
  • Mysql分組查詢group by使用示例
    (1) group by的含義:將查詢結果按照1個或多個欄位進行分組,欄位值相同的為一組(2) group by可用於單個欄位分組
  • 神奇的 SQL → 為什麼 GROUP BY 之後不能直接引用原表中的列?
    =only_full_group_by提示信息:SELECT 列表中的第二個表達式(cname)不在 GROUP BY 的子句中,同時它也不是聚合函數;這與 sql 模式:ONLY_FULL_GROUP_BY 不相容。
  • SQL實例整理
    的SQL教程(http://www.w3school.com.cn/sql/sql_create_table.asp)都基本看過一遍的猿友閱讀。by student_name having min(score)>80;第二種方法是group by 、min函數 結合 having的使用,w3school教程裡面也提到過(在 SQL 中增加 HAVING 子句原因是,WHERE 關鍵字無法與合計函數一起使用)似乎看懂了,但是還是沒有自己運行一遍深刻!!!
  • 鞏固SQL - 窗口函數&變量&數據透視圖
    但作為合格的一個數據分析師,sql的精通肯定是必不可少的,所以最近瘋狂刷sql題,同時也來總結下我以前比較少用的語法。其他類lag(列名,往前的行數,[行數為null時的默認值,不指定為null])lead(列名,往後的行數,[行數為null時的默認值,不指定為null])ntile(n) 用於將分組數據按照順序切分成n片,返回當前切片值,如果切片不均勻,默認增加第一個切片的分布。3、窗口函數的功能1、同時具備分組和排序的功能。
  • 日進一步第三天,SQL語句之order by
    每日進步的第二天,持續日更,做產品助理的斜槓青年數據分析之小白的第一條sql語句第二天的group by則是方便快捷分組語句數據分析之小白的第二條sql語句比如執行order by price desc那麼就可以對數據進行降序排列。
  • ​SQL數據分析GROUP BY語句這樣用
    GROUP BY語句從英文的字面意義上理解就是根據(by)一定的規則進行分組(Group)數據處理或統計。
  • 【資料庫】group by的用法實例
    如圖有這樣一張成績表:首先要理解group by 含義:「Group By」從字面意義上理解就是根據「By」指定的規則對數據進行分組,所謂的分組就是將一個「數據集」劃分成若干個「小區域」,然後針對若干個「小區域」進行數據處理。
  • 使用SQL理解Django中的Group By
    其文檔提供了各種示例和備忘表,演示了如何使用ORM對數據進行分組和聚合,但我決定從另一個角度來處理這個問題。在本文中,我將查詢集和SQL並排放在一起。如果你更熟悉SQL,那麼這就是你的Django GROUP BY速查表。