一文詳解Java中的Stream的匯總和分組操作
前言
在前面的文章中其實(shí)大家也已經(jīng)看到我使用過(guò)collect(Collectors.toList())
將數(shù)據(jù)最后匯總成一個(gè) List 集合。
但其實(shí)還可以轉(zhuǎn)換成Integer、Map、Set 集合等。
一、查找流中的最大值和最小值
static List<Student> students = new ArrayList<>(); ? static { students.add(new Student("學(xué)生A", "大學(xué)A", 18, 98.0)); students.add(new Student("學(xué)生B", "大學(xué)A", 18, 91.0)); students.add(new Student("學(xué)生C", "大學(xué)A", 18, 90.0)); students.add(new Student("學(xué)生D", "大學(xué)B", 18, 76.0)); students.add(new Student("學(xué)生E", "大學(xué)B", 18, 91.0)); students.add(new Student("學(xué)生F", "大學(xué)B", 19, 65.0)); students.add(new Student("學(xué)生G", "大學(xué)C", 20, 80.0)); students.add(new Student("學(xué)生H", "大學(xué)C", 21, 78.0)); students.add(new Student("學(xué)生I", "大學(xué)C", 20, 67.0)); students.add(new Student("學(xué)生J", "大學(xué)D", 22, 87.0)); } ? public static void main(String[] args) { Optional<Student> collect1 = students.stream().collect(Collectors.maxBy((s1, s2) -> s1.getAge() - s2.getAge())); Optional<Student> collect2 = students.stream().collect(Collectors.minBy((s1, s2) -> s1.getAge() - s2.getAge())); Student max = collect1.get(); Student min = collect2.get(); System.out.println("max年齡的學(xué)生==>" + max); System.out.println("min年齡的學(xué)生==>" + min); /** * max年齡的學(xué)生==>Student(name=學(xué)生J, school=大學(xué)D, age=22, score=87.0) * min年齡的學(xué)生==>Student(name=學(xué)生A, school=大學(xué)A, age=18, score=98.0) */ }
Optional,它是一個(gè)容器,可以包含也可以不包含值。它是java8中人們常說(shuō)的優(yōu)雅的判空的操作。
另一個(gè)常見(jiàn)的返回單個(gè)值的歸約操作是對(duì)流中對(duì)象的一個(gè)數(shù)值字段求和?;蛘吣憧赡芟胍?平均數(shù)。這種操作被稱(chēng)為匯總操作。讓我們來(lái)看看如何使用收集器來(lái)表達(dá)匯總操作。
二、匯總
Collectors類(lèi)專(zhuān)門(mén)為匯總提供了一些個(gè)工廠方法:
當(dāng)然除此之外還有 求平均數(shù)averagingDouble
、求總數(shù)counting
等等
我們暫且就先以summingDouble
和summarizingDouble
來(lái)舉例吧
案例數(shù)據(jù)仍然是上面的那些student數(shù)據(jù)...
求全部學(xué)生成績(jī)的總分,求全部學(xué)生的平均分。
1、首先使用summingDouble
和 averagingDouble
來(lái)實(shí)現(xiàn)
Double summingScore = students.stream().collect(Collectors.summingDouble(Student::getScore)); Double averagingScore = students.stream().collect(Collectors.averagingDouble(Student::getScore)); System.out.println("學(xué)生的總分==>" + summingScore); System.out.println("學(xué)生的平均分==>" + averagingScore); /** * 學(xué)生的總分==>823.0 * 學(xué)生的平均分==>82.3 */
2、使用summarizingDouble
來(lái)實(shí)現(xiàn)
它更為綜合,可以直接計(jì)算出相關(guān)的匯總信息
DoubleSummaryStatistics summarizingDouble = students.stream().collect(Collectors.summarizingDouble(Student::getScore)); ? double sum = summarizingDouble.getSum(); long count = summarizingDouble.getCount(); double average = summarizingDouble.getAverage(); double max = summarizingDouble.getMax(); double min = summarizingDouble.getMin(); System.out.println("sum==>"+sum); System.out.println("count==>"+count); System.out.println("average==>"+average); System.out.println("max==>"+max); System.out.println("min==>"+min); /** * sum==>823.0 * count==>10 * average==>82.3 * max==>98.0 * min==>65.0 */
但其實(shí)大家也都發(fā)現(xiàn)了,使用一個(gè)接口能夠?qū)崿F(xiàn),也可以拆開(kāi)根據(jù)自己的所需,選擇合適的API來(lái)實(shí)現(xiàn),具體的使用還是需要看使用場(chǎng)景。
三、連接字符串
Joining,就是把流中每一個(gè)對(duì)象應(yīng)用toString方法得到的所有字符串連接成一個(gè)字符串。
如果這么看,它其實(shí)沒(méi)啥用,但是Java也留下了后招,它的同伴(重載方法)提供了一個(gè)可以接受元素之間的分割符的方法。
? String studentsName = students.stream().map(student -> student.getName()).collect(Collectors.joining()); System.out.println(studentsName); String studentsName2 = students.stream().map(student -> student.getName()).collect(Collectors.joining(",")); System.out.println(studentsName2); /** * 學(xué)生A學(xué)生B學(xué)生C學(xué)生D學(xué)生E學(xué)生F學(xué)生G學(xué)生H學(xué)生I學(xué)生J * 學(xué)生A,學(xué)生B,學(xué)生C,學(xué)生D,學(xué)生E,學(xué)生F,學(xué)生G,學(xué)生H,學(xué)生I,學(xué)生J */
對(duì)于對(duì)象的打?。?/p>
// 不過(guò)對(duì)于對(duì)象的打印 個(gè)人感覺(jué)還好 哈哈 String collect = students.stream().map(student -> student.toString()).collect(Collectors.joining(",")); System.out.println(collect); System.out.println(students); /** * Student(name=學(xué)生A, school=大學(xué)A, age=18, score=98.0),Student(name=學(xué)生B, school=大學(xué)A, age=18, score=91.0),Student(name=學(xué)生C, school=大學(xué)A, age=18, score=90.0),Student(name=學(xué)生D, school=大學(xué)B, age=18, score=76.0),Student(name=學(xué)生E, school=大學(xué)B, age=18, score=91.0).... * [Student(name=學(xué)生A, school=大學(xué)A, age=18, score=98.0), Student(name=學(xué)生B, school=大學(xué)A, age=18, score=91.0), Student(name=學(xué)生C, school=大學(xué)A, age=18, score=90.0), Student(name=學(xué)生D, school=大學(xué)B, age=18, score=76.0)..)] */
但其實(shí)我還有一些沒(méi)有講到的API使用方法,大家也可以額外去嘗試嘗試,這其實(shí)遠(yuǎn)比你看這篇文章吸收的更快~
四、分組
就像數(shù)據(jù)庫(kù)中的分組統(tǒng)計(jì)一樣~
1、分組
舉個(gè)例子,我想統(tǒng)計(jì)每個(gè)學(xué)校有哪些學(xué)生
我是不是得設(shè)計(jì)這樣的一個(gè)數(shù)據(jù)結(jié)構(gòu)Map<String,List<Student>>
才能存放勒,我在循環(huán)的時(shí)候,是不是每次都得判斷一下學(xué)生所在的學(xué)校的名稱(chēng),然后看是否要給它添加到這個(gè)List集合中去,最后再put到map中去呢?
看著就特別繁瑣,但是在 stream 中就變成了一行代碼,其他的東西,都是 Java 內(nèi)部給你優(yōu)化了。
// 我想知道每所學(xué)校中,學(xué)生的數(shù)量及相關(guān)信息,只要這一行代碼即可 Map<String, List<Student>> collect = students.stream().collect(Collectors.groupingBy(Student::getSchool)); System.out.println(collect); /** * {大學(xué)B=[Student(name=學(xué)生D, school=大學(xué)B, age=18, score=76.0), Student(name=學(xué)生E, school=大學(xué)B, age=18, score=91.0), Student(name=學(xué)生F, school=大學(xué)B, age=19, score=65.0)], * 大學(xué)A=[Student(name=學(xué)生A, school=大學(xué)A, age=18, score=98.0), Student(name=學(xué)生B, school=大學(xué)A, age=18, score=91.0), Student(name=學(xué)生C, school=大學(xué)A, age=18, score=90.0)], * 大學(xué)D=[Student(name=學(xué)生J, school=大學(xué)D, age=22, score=87.0)], * 大學(xué)C=[Student(name=學(xué)生G, school=大學(xué)C, age=20, score=80.0), Student(name=學(xué)生H, school=大學(xué)C, age=21, score=78.0), Student(name=學(xué)生I, school=大學(xué)C, age=20, score=67.0)]} */
有些時(shí)候這真的是十分有用且方便的。
但是有時(shí)候我們往往不止于如此,假如我要統(tǒng)計(jì)每個(gè)學(xué)校中20歲年齡以上和20以下的學(xué)生分別有哪些學(xué)生,那么我的參數(shù)就不再是Student::getSchool
了,而是要加上語(yǔ)句了。那么該如何編寫(xiě)呢?
//統(tǒng)計(jì)每個(gè)學(xué)校中20歲年齡以上和20以下的學(xué)生分別有多少 Map<String, List<Student>> collect = students.stream().collect(Collectors.groupingBy(student -> { if (student.getAge() > 20) { return "20歲以上的"; } return "20以下的"; })); System.out.println(collect);
如果要統(tǒng)計(jì)每個(gè)學(xué)校有多少20歲以上和20歲以下的學(xué)生的信息,其實(shí)也就是把 return 語(yǔ)句修改以下即可。
//統(tǒng)計(jì)每個(gè)學(xué)校中20歲年齡以上和20以下的學(xué)生分別有多少 Map<String, List<Student>> collect = students.stream().collect(Collectors.groupingBy(student -> { if (student.getAge() > 20) { return student.getSchool(); } return student.getSchool(); })); System.out.println(collect);
相信大家也看出來(lái)groupingBy
中的 return 語(yǔ)句就是 Map 中的key值
2、多級(jí)分組
但其實(shí)groupingBy()
并不只是一個(gè)人,它也有兄弟姐妹
假如我想把上面的例子再改造改造,
改為:我想知道20歲以上的學(xué)生在每個(gè)學(xué)校有哪些學(xué)生,20歲以下的學(xué)生在每個(gè)學(xué)校有哪些學(xué)生。
數(shù)據(jù)結(jié)構(gòu)就應(yīng)當(dāng)設(shè)計(jì)為Map<String, Map<String, List<Student>>>
啦,第一級(jí)存放 20歲以上以下兩組數(shù)據(jù),第二級(jí)存放以每個(gè)學(xué)校名為key的數(shù)據(jù)信息。
Map<String, Map<String, List<Student>>> collect = students.stream().collect(Collectors.groupingBy(Student::getSchool, Collectors.groupingBy(student -> { if (student.getAge() > 20) { return "20以上的"; } return "20歲以下的"; }))); System.out.println(collect); /** * {大學(xué)B={20歲以下的=[Student(name=學(xué)生D, school=大學(xué)B, age=18, score=76.0),Student(name=學(xué)生E, school=大學(xué)B, age=18, score=91.0), Student(name=學(xué)生F, school=大學(xué)B, age=19, score=65.0)]}, * 大學(xué)A={20歲以下的=[Student(name=學(xué)生A, school=大學(xué)A, age=18, score=98.0), Student(name=學(xué)生B, school=大學(xué)A, age=18, score=91.0), Student(name=學(xué)生C, school=大學(xué)A, age=18, score=90.0)]}, * 大學(xué)D={20以上的=[Student(name=學(xué)生J, school=大學(xué)D, age=22, score=87.0)]}, * 大學(xué)C={20以上的=[Student(name=學(xué)生H, school=大學(xué)C, age=21, score=78.0)],20歲以下的=[Student(name=學(xué)生G, school=大學(xué)C, age=20, score=80.0), Student(name=學(xué)生I, school=大學(xué)C, age=20, score=67.0)]}} */
這里利用的就是把一個(gè)內(nèi)層groupingBy傳遞給外層groupingBy,俗稱(chēng)的套娃~
外層Map的鍵就是第一級(jí)分類(lèi)函數(shù)生成的值,而這個(gè)Map的值又是一個(gè)Map,鍵是二級(jí)分類(lèi)函數(shù)生成的值。
3、按子組數(shù)據(jù)進(jìn)行劃分
之前我的截圖中,groupingBy的重載方法中,其實(shí)對(duì)于第二個(gè)參數(shù)的限制,并非說(shuō)一定是要groupingBy
類(lèi)型的收集,更抽象點(diǎn)說(shuō),它可以是任意的收集器~
再假如,我的例子改為:
我現(xiàn)在明確的想知道每個(gè)學(xué)校20歲的學(xué)生的人數(shù)。
那么這個(gè)數(shù)據(jù)結(jié)構(gòu)就應(yīng)當(dāng)改為
Map<String,Long>
或者是Map<String,Integer>
呢?
那么在這里該如何實(shí)現(xiàn)呢?
Map<String, Long> collect = students.stream().collect(Collectors.groupingBy(Student::getSchool, Collectors.counting())); System.out.println(collect); /** * {大學(xué)B=3, 大學(xué)A=3, 大學(xué)D=1, 大學(xué)C=3} */
實(shí)際上還有許多未曾談到的東西,這里都只是非常簡(jiǎn)單的應(yīng)用,對(duì)于其中的流的執(zhí)行的先后順序,以及一些簡(jiǎn)單的原理,都沒(méi)有過(guò)多的涉及,大家先上手用著吧~
后記
我這里只是闡述了一些比較簡(jiǎn)單的應(yīng)用性操作,未談及設(shè)計(jì)思想之類(lèi),但是要明白那種才是更值得去閱讀和理解的。
到此這篇關(guān)于一文詳解Java中的Stream的匯總和分組操作的文章就介紹到這了,更多相關(guān)Java Stream匯總 分組內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mybatis 忽略實(shí)體對(duì)象的某個(gè)屬性(2種方式)
這篇文章主要介紹了mybatis 忽略實(shí)體對(duì)象的某個(gè)屬性方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06JavaBean實(shí)體類(lèi)處理外鍵過(guò)程解析
這篇文章主要介紹了JavaBean實(shí)體類(lèi)處理外鍵過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07SpringBoot2.0+阿里巴巴Sentinel動(dòng)態(tài)限流實(shí)戰(zhàn)(附源碼)
這篇文章主要介紹了SpringBoot2.0+阿里巴巴Sentinel動(dòng)態(tài)限流實(shí)戰(zhàn)(附源碼),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11Java構(gòu)造函數(shù)里的一些坑記錄super()和this()
這篇文章主要介紹了Java構(gòu)造函數(shù)里的一些坑記錄super()和this(),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03Java數(shù)組使用binarySearch()方法查找指定元素的實(shí)現(xiàn)
這篇文章主要介紹了Java數(shù)組使用binarySearch()方法查找指定元素的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01Java之Spring簡(jiǎn)單的讀取和存儲(chǔ)對(duì)象
這篇文章主要介紹了Spring的讀取和存儲(chǔ)對(duì)象,獲取 bean 對(duì)象也叫做對(duì)象裝配,是把對(duì)象取出來(lái)放到某個(gè)類(lèi)中,有時(shí)候也叫對(duì)象注?,想進(jìn)一步了解的同學(xué)可以參考本文2023-04-04SpringBoot+WebSocket實(shí)現(xiàn)消息推送功能
WebSocket協(xié)議是基于TCP的一種新的網(wǎng)絡(luò)協(xié)議。本文將通過(guò)SpringBoot集成WebSocket實(shí)現(xiàn)消息推送功能,感興趣的可以了解一下2022-08-08