黑客日教程-Java8新功能:將數(shù)據(jù)集合進行分組,類似SQL的GROUP BY

1 介紹

本文將展示groupingBy收集器的多個示例。
閱讀本文需要先準備Java Stream和Java收集器Collector的知識。

2 GroupingBy收集器

Java8的Stream API允許我們以聲明的方式來處理數(shù)據(jù)集合。
靜態(tài)工廠方法:Collectors.groupingBy(),以及Collectors.groupingByConcunrrent(),給我們提供了類似SQL語句中的"GROUP BY"的功能。這兩個方法將數(shù)據(jù)按某些屬性分組,并存儲在Map中返回。
下面是幾個重載的groupnigBy方法:

  • 參數(shù):分類函數(shù)
static <T,K> Collector<T,?,Map<K,List<T>>> 
  groupingBy(Function<? super T,? extends K> classifier)
  • 參數(shù):分類函數(shù),第二個收集器
static <T,K,A,D> Collector<T,?,Map<K,D>>
  groupingBy(Function<? super T,? extends K> classifier, 
    Collector<? super T,A,D> downstream)
  • 參數(shù):分類函數(shù),供應(yīng)者方法(提供作為返回值的Map的實現(xiàn)),第二個收集器
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 根據(jù)單一字段分組

最簡單的groupingBy方法,只有一個分類函數(shù)做參數(shù)。分類函數(shù)作用于strema里面的每個元素。分類函數(shù)處理后返回的每個元素作為返回Map的key。
根據(jù)博客文章類型來分組:

Map<BlogPostType, List<BlogPost>> postsPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType));

2.3 根據(jù)Map的key的類型分組

分類函數(shù)并沒有限制返回字符串或標量值。返回map的key可以是任何對象。只要實現(xiàn)了其equals和hashcode方法。
下面示例根據(jù)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參數(shù)(downstream),此參數(shù)作用于第一個collector產(chǎn)生的結(jié)果。
如果只用一個分類函數(shù)做參數(shù),那么默認會使用toList()這個collector來轉(zhuǎn)換結(jié)果。
下面的代碼顯示地使用了toSet()這個collector傳遞給downstream這個參數(shù),因此會得到一個博客文章的Set。

Map<BlogPostType, Set<BlogPost>> postsPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType, toSet()));

2.5 根據(jù)多個字段分組

downstream參數(shù)的另外一個用處就是基于分組結(jié)果,做第二次分組。
下面代碼,首先根據(jù)author分組,然后再根據(jù)type分組:

Map<String, Map<BlogPostType, List>> map = posts.stream()
  .collect(groupingBy(BlogPost::getAuthor, groupingBy(BlogPost::getType)));

2.6 得到分組結(jié)果的平均值

通過使用downstream,我們可以把集合函數(shù)應(yīng)用到第一次分組的結(jié)果上。比如,獲取到每種類型博客的被喜歡次數(shù)(likes)的平均值:

Map<BlogPostType, Double> averageLikesPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType, averagingInt(BlogPost::getLikes)));

2.7 得到分組結(jié)果的總計

計算每種類型被喜歡次數(shù)的總數(shù):

Map<BlogPostType, Integer> likesPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType, summingInt(BlogPost::getLikes)));

2.8 得到分組結(jié)果中的最大或最小值

我們還可以得到每種類型博客被喜歡次數(shù)最多的是多少:

Map<BlogPostType, Optional<BlogPost>> maxLikesPerPostType = posts.stream()
  .collect(groupingBy(BlogPost::getType,
  maxBy(comparingInt(BlogPost::getLikes))));

類似的,可以用minxBy得到每種類型博客中被喜歡次數(shù)最少的次數(shù)是多少。
注意:maxBy和minBy都考慮了當(dāng)?shù)谝淮畏纸M得到的結(jié)果是空的場景,因此其返回結(jié)果(Map的value)是Optional<BlogPost>。

2.9 得到分組結(jié)果中某個屬性的統(tǒng)計

Collectors API提供了一個統(tǒng)計collector,可以用來同時計算數(shù)量、總計、最小值、最大值、平均值等。
下面來統(tǒng)計一下不同類型博客的被喜歡(likes)這個屬性:

Map<BlogPostType, IntSummaryStatistics> likeStatisticsPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType, 
  summarizingInt(BlogPost::getLikes)));

返回Map中的value,IntSummaryStatistics對象,包括了每個BlogPostType的文章次數(shù)、被喜歡總計、平均值、最大值、最小值。

2.10 把分組結(jié)果映射為另外的類型

更復(fù)雜的聚合操作可以通過應(yīng)用一個映射downstream收集器到分類函數(shù)結(jié)果上來實現(xiàn)。
下面代碼講每類博客類型的標題連接起來了。

Map<BlogPostType, String> postsPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType, 
  mapping(BlogPost::getTitle, joining(", ", "Post titles: [", "]"))));

上面的代碼,講每個BlogPost實例映射為了其對應(yīng)的標題,然后把博客標題的stream連接成了成了字符串,形如“Post titles:[標題1,標題2,標題3]”。

2.11 修改返回Map的類型

使用groupingBy的時候,如果我們要指定返回Map的具體類型,可以用第三個重載方法。通過傳入一個Map供應(yīng)者函數(shù)。
下面代碼傳入了一個EnumMap供應(yīng)者函數(shù),得到返回Map為EnumMap類型。

EnumMap<BlogPostType, List<BlogPost>> postsPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType, 
  () -> new EnumMap<>(BlogPostType.class), toList()));

3 并發(fā)的分組Collector

類似groupingBy,存在一個groupingByConcurrent收集器,可以利用到多核架構(gòu)的能力。groupingByConcurrent也有3個重載的方法,與groupingBy類似。
但返回值必須是ConconcurrentHashMap或其子類。
要并發(fā)操作分組,那么stream也必須是并行的:

ConcurrentMap<BlogPostType, List<BlogPost>> postsPerType = posts.parallelStream()
  .collect(groupingByConcurrent(BlogPost::getType));

注意:如果要提供一個Map供應(yīng)者函數(shù),必須保證函數(shù)返回的是ConconcurrentHashMap或其子類。

4 Java 9新功能

java9引入兩個新的收集器可以在goupingBy中使用的:更多詳情

5 小結(jié)

本文討論了Java 8 Collectors API中的groupingBy收集器的幾個例子。
討論了goupingBy如何對stream中的元素基于某個屬性進行分組,以及如何返回結(jié)果。
示例代碼見github

編譯

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容