Stream Collectors.groupingBy的四種用法 解決分組統(tǒng)計(計數净薛、求和、平均數等)蒲拉、范圍統(tǒng)計肃拜、分組合并、分組結果自定義映射等問題

前言
近期雌团,由于業(yè)務需要燃领,會統(tǒng)計一些簡單的頁面指標,如果每個統(tǒng)計都通過SQL實現的話锦援,又略感枯燥乏味猛蔽。于是選擇使用Stream的分組功能。對于這些簡單的統(tǒng)計指標來說灵寺,Stream的分組更為靈活曼库,只需要提取出需要統(tǒng)計的數據,便可以對這些數據進行任意處理略板,而無需再次編寫不同的SQL去統(tǒng)計不同的指標毁枯。

此文主要是總結我在此前的工作中使用到的Collectors.groupingBy的一些方法和技巧。根據平時使用的習慣叮称,將Collectors.groupingBy的功能大致分為四種种玛,但這種界定都是模糊的胀糜,并不是絕對,每種功能都可以穿插使用蒂誉,這里只是更方便了解Collectors.groupingBy各個方法的使用規(guī)則。

四種分組功能如下:

基礎分組功能
分組統(tǒng)計功能
分組合并功能
分組自定義映射功能

Stream的其它用法可以參考下文:

超詳細的Java8 Stream使用方法:篩選距帅、排序右锨、最大值、最小值碌秸、計數求和平均數绍移、分組、合并讥电、映射蹂窖、去重等

語法說明
基礎語法
Collector<T, ?, Map<K, List<T>>> groupingBy(Function<? super T, ? extends K> classifier)

Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream)

Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier, Supplier<M> mapFactory, Collector<? super T, A, D> downstream)

classifier:鍵映射:該方法的返回值是鍵值對的 鍵
mapFactory:無參構造函數提供返回類型:提供一個容器初始化方法,用于創(chuàng)建新的 Map容器 (使用該容器存放值對)恩敌。
downstream:值映射:通過聚合方法將同鍵下的結果聚合為指定類型瞬测,該方法返回的是鍵值對的 值。

前置數據
List<Student> students = Stream.of(
Student.builder().name("小張").age(16).clazz("高一1班").course("歷史").score(88).build(),
Student.builder().name("小李").age(16).clazz("高一3班").course("數學").score(12).build(),
Student.builder().name("小王").age(17).clazz("高二1班").course("地理").score(44).build(),
Student.builder().name("小紅").age(18).clazz("高二1班").course("物理").score(67).build(),
Student.builder().name("李華").age(15).clazz("高二2班").course("數學").score(99).build(),
Student.builder().name("小潘").age(19).clazz("高三4班").course("英語").score(100).build(),
Student.builder().name("小聶").age(20).clazz("高三4班").course("物理").score(32).build()
).collect(Collectors.toList());
1
2
3
4
5
6
7
8
9

分組的4種使用方法

  1. 基礎分組功能
    說明:基礎功能纠炮,分組并返回Map容器月趟。將用戶自定義的元素作為鍵,同時將鍵相同的元素存放在List中作為值恢口。

Collectors.groupingBy:基礎分組功能
下面的寫法都是等價的

// 將不同課程的學生進行分類
Map<String, List<Student>> groupByCourse = students.stream().collect(Collectors.groupingBy(Student::getCourse));
Map<String, List<Student>> groupByCourse1 = students.stream().collect(Collectors.groupingBy(Student::getCourse, Collectors.toList()));
// 上面的方法中容器類型和值類型都是默認指定的孝宗,容器類型為:HashMap,值類型為:ArrayList
// 可以通過下面的方法自定義返回結果耕肩、值的類型
Map<String, List<Student>> groupByCourse2 = students.stream()
.collect(Collectors.groupingBy(Student::getCourse, HashMap::new, Collectors.toList()));
1
2
3
4
5
6
7
第三種寫法可以自定義鍵類型因妇、容器類型、值類型猿诸。
如需要保證students分組后的有序性的話婚被,那么可以自定義容器類型為LinkedHashMap。

后文的其它三個功能點两芳,都基于第三個參數:Collector<? super T, A, D> downstream摔寨。換句話說,他們都是通過實現Collector接口來實現各種downstream操作的怖辆。

  1. 分組統(tǒng)計功能
    說明:分組后是复,對同一分組內的元素進行計算:計數、平均值竖螃、求和淑廊、最大最小值、范圍內數據統(tǒng)計特咆。

Collectors.counting:計數
計數語法:
Collector<T, ?, Long> counting()

// 計數
Map<String, Long> groupCount = students.stream()
.collect(Collectors.groupingBy(Student::getCourse, Collectors.counting()));
1
2
3

Collectors.summingInt:求和
求和語法:
Collector<T, ?, Integer> summingInt(ToIntFunction<? super T> mapper)
Collector<T, ?, Long> summingLong(ToLongFunction<? super T> mapper)
Collector<T, ?, Double> summingDouble(ToDoubleFunction<? super T> mapper)

求和針對流中元素類型的不同季惩,分別提供了三種計算方式:Int录粱、Double、Long画拾。計算方式與計算結果必須與元素類型匹配啥繁。

// 求和
Map<String, Integer> groupSum = students.stream()
.collect(Collectors.groupingBy(Student::getCourse, Collectors.summingInt(Student::getScore)));
1
2
3

Collectors.averagingInt:平均值
平均值語法:
Collector<T, ?, Double> averagingInt(ToIntFunction<? super T> mapper)
Collector<T, ?, Double> averagingLong(ToLongFunction<? super T> mapper)
Collector<T, ?, Double> averagingDouble(ToDoubleFunction<? super T> mapper)

平均值計算關注點:

平均值有三種計算方式:Int、Double青抛、Long旗闽。
計算方式僅對計算結果的精度有影響。
計算結果始終返回Double蜜另。
// 增加平均值計算
Map<String, Double> groupAverage = students.stream()
.collect(Collectors.groupingBy(Student::getCourse, Collectors.averagingInt(Student::getScore)));
1
2
3

Collectors.minBy:最大最小值
最大最少值語法:
Collector<T, ?, Optional<T>> minBy(Comparator<? super T> comparator)
Collector<T, ?, Optional<T>> maxBy(Comparator<? super T> comparator)

Collectors.collectingAndThen語法:
Collector<T,A,RR> collectingAndThen(Collector<T,A,R> downstream, Function<R,RR> finisher)

Function<R,RR>:提供參數類型為R适室,返回結果類型為RR。
Collectors.minBy方法返回的類型為Optional<T>>举瑰,在取數據時還需要校驗Optional是否為空捣辆。

不過這一步可以通過Collectors.collectingAndThen方法實現,并返回校驗結果此迅。Collectors.collectingAndThen的作用便是在使用聚合函數之后汽畴,對聚合函數的結果進行再加工佛寿。

// 同組最小值
Map<String, Optional<Student>> groupMin = students.stream()
.collect(Collectors.groupingBy(Student::getCourse,Collectors.minBy(Comparator.comparing(Student::getCourse))));
// 使用Collectors.collectingAndThen方法仲闽,處理Optional類型的數據
Map<String, Student> groupMin2 = students.stream()
.collect(Collectors.groupingBy(Student::getCourse,
Collectors.collectingAndThen(Collectors.minBy(Comparator.comparing(Student::getCourse)), op ->op.orElse(null))));
// 同組最大值
Map<String, Optional<Student>> groupMax = students.stream()
.collect(Collectors.groupingBy(Student::getCourse,Collectors.maxBy(Comparator.comparing(Student::getCourse))));
1
2
3
4
5
6
7
8
9
10

Collectors.summarizingInt:完整統(tǒng)計(同時獲取以上的全部統(tǒng)計結果)
完整統(tǒng)計語法:
Collector<T, ?, IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper)
Collector<T, ?, LongSummaryStatistics> summarizingLong(ToLongFunction<? super T> mapper)
Collector<T, ?, DoubleSummaryStatistics> summarizingDouble(ToDoubleFunction<? super T> mapper)

統(tǒng)計方法提供了三種計算方式:Int糕珊、Double糠惫、Long蚪缀。它會將輸入元素轉為上述三種計算方式的基本類型唱歧,然后進行計算亏栈。Collectors.summarizingXXX方法可以計算一般統(tǒng)計所需的所有結果溉浙。

無法向下轉型芋忿,即Long無法轉Int等炸客。

返回結果取決于用的哪種計算方式。

// 統(tǒng)計方法同時統(tǒng)計同組的最大值戈钢、最小值痹仙、計數、求和殉了、平均數信息
HashMap<String, IntSummaryStatistics> groupStat = students.stream()
.collect(Collectors.groupingBy(Student::getCourse, HashMap::new,Collectors.summarizingInt(Student::getScore)));
groupStat.forEach((k, v) -> {
// 返回結果取決于用的哪種計算方式
v.getAverage();
v.getCount();
v.getMax();
v.getMin();
v.getSum();
});
1
2
3
4
5
6
7
8
9
10
11

Collectors.partitioningBy:范圍統(tǒng)計
Collectors.partitioningBy語法:
Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate)
Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate, Collector<? super T, A, D> downstream)

predicate:條件參數开仰,對分組的結果劃分為兩個范圍。
上面的統(tǒng)計都是基于某個指標項的薪铜。如果我們需要統(tǒng)計范圍众弓,比如:得分大于、小于60分的人的信息隔箍,那么我們可以通過Collectors.partitioningBy方法對映射結果進一步切分

// 切分結果谓娃,同時統(tǒng)計大于60和小于60分的人的信息
Map<String, Map<Boolean, List<Student>>> groupPartition = students.stream()
.collect(Collectors.groupingBy(Student::getCourse, Collectors.partitioningBy(s -> s.getScore() > 60)));
// 同樣的,我們還可以對上面兩個分組的人數數據進行統(tǒng)計
Map<String, Map<Boolean, Long>> groupPartitionCount = students.stream()
.collect(Collectors.groupingBy(Student::getCourse, Collectors.partitioningBy(s -> s.getScore() > 60, Collectors.counting())));

1
2
3
4
5
6
7
Collectors.partitioningBy僅支持將數據劃分為兩個范圍進行統(tǒng)計蜒滩,如果需要劃分多個滨达,可以嵌套Collectors.partitioningBy執(zhí)行奶稠,不過需要在執(zhí)行完后,手動處理不需要的數據捡遍。也可以在第一次Collectors.partitioningBy獲取結果后锌订,再分別對該結果進行范圍統(tǒng)計。

Map<String, Map<Boolean, Map<Boolean, List<Student>>>> groupAngPartitionCount = students.stream()
.collect(Collectors.groupingBy(Student::getCourse, Collectors.partitioningBy(s -> s.getScore() > 60,
Collectors.partitioningBy(s -> s.getScore() > 90))));
1
2
3

  1. 分組合并功能
    說明:將同一個鍵下的值画株,通過不同的方法最后合并為一條數據瀑志。

Collectors.reducing:合并分組結果
Collectors.reducing語法:
Collector<T, ?, Optional> reducing(BinaryOperator op)
Collector<T, ?, T> reducing(T identity, BinaryOperator op)
Collector<T, ?, U> reducing(U identity, Function<? super T, ? extends U> mapper, BinaryOperator op)

identity:合并標識值(因子),它將參與累加函數和合并函數的運算(即提供一個默認值污秆,在流為空時返回該值,當流不為空時昧甘,該值作為起始值良拼,參與每一次累加或合并計算)
mapper:映射流中的某個元素,并根據此元素進行合并充边。
op:合并函數庸推,將mapper映射的元素,進行兩兩合并浇冰,最初的一個元素將于合并標識值進行合并贬媒。
// 合并結果,計算每科總分
Map<String, Integer> groupCalcSum = students.stream()
.collect(Collectors.groupingBy(Student::getCourse, Collectors.reducing(0, Student::getScore, Integer::sum)));
// 合并結果肘习,獲取每科最高分的學生信息
Map<String, Optional<Student>> groupCourseMax = students.stream()
.collect(Collectors.groupingBy(Student::getCourse, Collectors.reducing(BinaryOperator.maxBy(Comparator.comparing(Student::getScore)))));
1
2
3
4
5
6

Collectors.joining:合并字符串
Collectors.joining語法:
Collector<CharSequence, ?, String> joining()
Collector<CharSequence, ?, String> joining(CharSequence delimiter)
Collector<CharSequence, ?, String> joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)

delimiter:分隔符
prefix:每個字符的前綴
suffix:每個字符的后綴
Collectors.joining只能對字符進行操作际乘,因此一般會與其它downstream方法組合使用。

// 統(tǒng)計各科的學生姓名
Map<String, String> groupCourseSelectSimpleStudent = students.stream()
.collect(Collectors.groupingBy(Student::getCourse, Collectors.mapping(Student::getName, Collectors.joining(","))));
1
2
3

  1. 分組自定義映射功能
    說明:實際上Collectors.groupingBy的第三個參數downstream漂佩,其實就是就是將元素映射為不同的值脖含。而且上面的所有功能都是基于downstream的。這一節(jié)投蝉,主要介紹一些方法來設置自定義值养葵。

Collectors.toXXX:映射結果為Collection對象
將結果映射為ArrayList:
Collector<T, ?, List> toList()

將結果映射為HashSet:
Collector<T, ?, Set> toSet()

將結果映射為HashMap或其他map類:
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction)
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)

keyMapper:key映射
valueMapper:value映射
mergeFunction:當流中的key重復時,提供的合并方式瘩缆,默認情況下关拒,將會拋出IllegalStateException異常。
mapSupplier:提供Map容器的無參初始化方式庸娱,可以自定義返回的Map容器類型着绊。
Collectors.toConcurrentMap的語法同Collectors.toMap,不過他們仍然有一些區(qū)別:

前者默認返回ConcurrentHashMap涌韩,后者返回HashMap
在處理并行流中存在差異:toMap會多次調用mapSupplier畔柔,產生多個map容器,最后在通過Map.merge()合并起來臣樱,而toConcurrentMap則只會調用一次靶擦,并且該容器將會不斷接受其他線程的調用以添加鍵值對腮考。在并發(fā)情況下,toMap容器合并的性能自然是不如toConcurrentMap優(yōu)秀的玄捕。
Map<String, Map<String, Integer>> courseWithStudentScore = students.stream()
.collect(Collectors.groupingBy(Student::getCourse, Collectors.toMap(Student::getName, Student::getScore)));
Map<String, LinkedHashMap<String, Integer>> courseWithStudentScore2 = students.stream()
.collect(Collectors.groupingBy(Student::getCourse, Collectors.toMap(Student::getName, Student::getScore, (k1, k2) -> k2, LinkedHashMap::new)));
1
2
3
4

Collectors.mapping:自定義映射結果
Collectors.mapping語法:
Collector<T, ?, R> mapping(Function<? super T, ? extends U> mapper, Collector<? super U, A, R> downstream)

Collectors.mapping的功能比較豐富踩蔚,除了可以將分組結果映射為自己想要的值外,還能組合上面提到的所有downstream方法枚粘。

將結果映射為指定字段:

Map<String, List<String>> groupMapping = students.stream()
.collect(Collectors.groupingBy(Student::getCourse, Collectors.mapping(Student::getName, Collectors.toList())));
1
2
轉換bean對象:

Map<String, List<OutstandingStudent>> groupMapping2 = students.stream()
.filter(s -> s.getScore() > 60)
.collect(Collectors.groupingBy(Student::getCourse, Collectors.mapping(s -> BeanUtil.copyProperties(s, OutstandingStudent.class), Collectors.toList())));
1
2
3
組合joining

// 組合joining
Map<String, String> groupMapperThenJoin= students.stream()
.collect(Collectors.groupingBy(Student::getCourse, Collectors.mapping(Student::getName, Collectors.joining(","))));
// 利用collectingAndThen處理joining后的結果
Map<String, String> groupMapperThenLink = students.stream()
.collect(Collectors.groupingBy(Student::getCourse,
Collectors.collectingAndThen(Collectors.mapping(Student::getName, Collectors.joining("馅闽,")), s -> "學生名單:" + s)));
1
2
3
4
5
6
7

Collector:自定義downstream
可以參考:【Java8 Stream】:探秘Stream實現的核心:Collector,模擬Stream的實現

Collector<T, A, R>范型的含義:

<T>:規(guī)約操作(reduction operation)的輸入元素類型
<A>:是規(guī)約操作的輸出結果類型馍迄,該類型是可變可累計的福也,可以是各種集合容器,或者具有累計操作(如add)的自定義對象攀圈。
<R>:規(guī)約操作結果經過轉換操作后返回的最終結果類型
Collector中方法定義暴凑,下面的方法的返回值都可以看作函數(function):

Supplier<A> supplier():該函數創(chuàng)建并返回新容器對象。
BiConsumer<A, T> accumulator():該函數將把元素值放入容器對象赘来,并返回容器现喳。
BinaryOperator<A> combiner():該函數會把兩個容器(此時每個容器都是處理流元素的部分結果)合并,該函數可以返回這兩個容器中的一個犬辰,也可以返回一個新的容器嗦篱。
Function<A, R> finisher():該函數將執(zhí)行最終的轉換,它會將combiner的最終合并結果A轉變?yōu)镽幌缝。
Set<Characteristics> characteristics():提供集合列表灸促,該列表將提供當前Collector的一些特征值。這些特征將會影響上述函數的表現涵卵。
上述函數的語法:

Supplier<T>#T get():調用一個無參方法腿宰,返回一個結果。一般來說是構造方法的方法引用缘厢。
BiConsumer<T, U>#void accept(T t, U u):根據給定的兩個參數吃度,執(zhí)行相應的操作。
BinaryOperator<T> extends BiFunction<T,T,T>#T apply(T t, T u):合并t和u贴硫,返回其中之一椿每,或創(chuàng)建一個新對象放回。
Function<T, R>#R apply(T t):處理給定的參數英遭,并返回一個新的值间护。
public interface Collector<T, A, R> {

Supplier<A> supplier();

BiConsumer<A, T> accumulator();

BinaryOperator<A> combiner();

Function<A, R> finisher();

Set<Characteristics> characteristics();

}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市挖诸,隨后出現的幾起案子汁尺,更是在濱河造成了極大的恐慌,老刑警劉巖多律,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件痴突,死亡現場離奇詭異搂蜓,居然都是意外死亡,警方通過查閱死者的電腦和手機辽装,發(fā)現死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門帮碰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人拾积,你說我怎么就攤上這事殉挽。” “怎么了拓巧?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵斯碌,是天一觀的道長。 經常有香客問我肛度,道長输拇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任贤斜,我火速辦了婚禮,結果婚禮上逛裤,老公的妹妹穿的比我還像新娘瘩绒。我一直安慰自己,他們只是感情好带族,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布锁荔。 她就那樣靜靜地躺著,像睡著了一般蝙砌。 火紅的嫁衣襯著肌膚如雪阳堕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天择克,我揣著相機與錄音恬总,去河邊找鬼。 笑死肚邢,一個胖子當著我的面吹牛壹堰,可吹牛的內容都是我干的。 我是一名探鬼主播骡湖,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼贱纠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了响蕴?” 一聲冷哼從身側響起谆焊,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎浦夷,沒想到半個月后辖试,有當地人在樹林里發(fā)現了一具尸體辜王,經...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年剃执,在試婚紗的時候發(fā)現自己被綠了誓禁。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡肾档,死狀恐怖摹恰,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情怒见,我是刑警寧澤俗慈,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站遣耍,受9級特大地震影響闺阱,放射性物質發(fā)生泄漏。R本人自食惡果不足惜舵变,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一酣溃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧纪隙,春花似錦赊豌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至悲伶,卻和暖如春艾恼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背麸锉。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工钠绍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人花沉。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓五慈,卻偏偏與公主長得像,于是被迫代替她去往敵國和親主穗。 傳聞我的和親對象是個殘疾皇子泻拦,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345