一文詳解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ù)。這種操作被稱為匯總操作。讓我們來(lái)看看如何使用收集器來(lái)表達(dá)匯總操作。
二、匯總
Collectors類專門為匯總提供了一些個(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é)校的名稱,然后看是否要給它添加到這個(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ǔ)句了。那么該如何編寫呢?
//統(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,俗稱的套娃~
外層Map的鍵就是第一級(jí)分類函數(shù)生成的值,而這個(gè)Map的值又是一個(gè)Map,鍵是二級(jí)分類函數(shù)生成的值。
3、按子組數(shù)據(jù)進(jìn)行劃分
之前我的截圖中,groupingBy的重載方法中,其實(shí)對(duì)于第二個(gè)參數(shù)的限制,并非說(shuō)一定是要groupingBy
類型的收集,更抽象點(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ì)思想之類,但是要明白那種才是更值得去閱讀和理解的。
到此這篇關(guān)于一文詳解Java中的Stream的匯總和分組操作的文章就介紹到這了,更多相關(guān)Java Stream匯總 分組內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringCloud Eureka服務(wù)發(fā)現(xiàn)實(shí)現(xiàn)過(guò)程
這篇文章主要介紹了SpringCloud Eureka服務(wù)發(fā)現(xiàn)實(shí)現(xiàn)過(guò)程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11Java對(duì)象創(chuàng)建內(nèi)存案例解析
這篇文章主要介紹了Java對(duì)象創(chuàng)建內(nèi)存案例解析,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08mybatis雙重foreach如何實(shí)現(xiàn)遍歷map中的兩個(gè)list數(shù)組
本文介紹了如何解析前端傳遞的JSON字符串并在Java后臺(tái)動(dòng)態(tài)構(gòu)建SQL查詢條件,首先,通過(guò)JSONArray.fromObject()將JSON字符串轉(zhuǎn)化為JSONArray對(duì)象,遍歷JSONArray,從中提取name和infos,構(gòu)建成Map對(duì)象用于Mybatis SQL映射2024-09-09SpringBoot如何優(yōu)雅實(shí)現(xiàn)接口參數(shù)驗(yàn)證
為了保證參數(shù)的正確性,我們需要使用參數(shù)驗(yàn)證機(jī)制,來(lái)檢測(cè)并處理傳入的參數(shù)格式是否符合規(guī)范,所以本文就來(lái)和大家聊聊如何優(yōu)雅實(shí)現(xiàn)接口參數(shù)驗(yàn)證吧2023-08-08Netty源碼解析NioEventLoop創(chuàng)建的構(gòu)造方法
這篇文章主要介紹了Netty源碼解析NioEventLoopGroup之NioEventLoop創(chuàng)建的構(gòu)造方法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03java讀取excel文件并復(fù)制(copy)文件到指定目錄示例
這篇文章主要介紹了java讀取excel文件并復(fù)制文件到指定目錄示例,需要的朋友可以參考下2014-02-02Java注解詳解及實(shí)現(xiàn)自定義注解的方法
這篇文章主要介紹了Java注解詳解及實(shí)現(xiàn)自定義注解的方法,本文給大家介紹了jdk中預(yù)定義的一些注解及自定義注解的相關(guān)知識(shí),需要的朋友可以參考下2022-06-06詳解Spring Cloud Gateway基于服務(wù)發(fā)現(xiàn)的默認(rèn)路由規(guī)則
這篇文章主要介紹了詳解Spring Cloud Gateway基于服務(wù)發(fā)現(xiàn)的默認(rèn)路由規(guī)則,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-05-05Java 實(shí)戰(zhàn)范例之線上婚紗攝影預(yù)定系統(tǒng)的實(shí)現(xiàn)
讀萬(wàn)卷書不如行萬(wàn)里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+javaweb+SSM+springboot+mysql實(shí)現(xiàn)一個(gè)線上婚紗攝影預(yù)定系統(tǒng),大家可以在過(guò)程中查缺補(bǔ)漏,提升水平2021-11-11