一文詳解Java中的Stream的匯總和分組操作
前言
在前面的文章中其實大家也已經(jīng)看到我使用過collect(Collectors.toList()) 將數(shù)據(jù)最后匯總成一個 List 集合。
但其實還可以轉(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,它是一個容器,可以包含也可以不包含值。它是java8中人們常說的優(yōu)雅的判空的操作。
另一個常見的返回單個值的歸約操作是對流中對象的一個數(shù)值字段求和?;蛘吣憧赡芟胍?平均數(shù)。這種操作被稱為匯總操作。讓我們來看看如何使用收集器來表達(dá)匯總操作。
二、匯總
Collectors類專門為匯總提供了一些個工廠方法:


當(dāng)然除此之外還有 求平均數(shù)averagingDouble、求總數(shù)counting等等
我們暫且就先以summingDouble和summarizingDouble來舉例吧
案例數(shù)據(jù)仍然是上面的那些student數(shù)據(jù)...
求全部學(xué)生成績的總分,求全部學(xué)生的平均分。
1、首先使用summingDouble 和 averagingDouble 來實現(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來實現(xiàn)
它更為綜合,可以直接計算出相關(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
*/
但其實大家也都發(fā)現(xiàn)了,使用一個接口能夠?qū)崿F(xiàn),也可以拆開根據(jù)自己的所需,選擇合適的API來實現(xiàn),具體的使用還是需要看使用場景。
三、連接字符串
Joining,就是把流中每一個對象應(yīng)用toString方法得到的所有字符串連接成一個字符串。
如果這么看,它其實沒啥用,但是Java也留下了后招,它的同伴(重載方法)提供了一個可以接受元素之間的分割符的方法。
?
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
*/
對于對象的打?。?/p>
// 不過對于對象的打印 個人感覺還好 哈哈
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)..)]
*/
但其實我還有一些沒有講到的API使用方法,大家也可以額外去嘗試嘗試,這其實遠(yuǎn)比你看這篇文章吸收的更快~
四、分組
就像數(shù)據(jù)庫中的分組統(tǒng)計一樣~
1、分組
舉個例子,我想統(tǒng)計每個學(xué)校有哪些學(xué)生
我是不是得設(shè)計這樣的一個數(shù)據(jù)結(jié)構(gòu)Map<String,List<Student>>才能存放勒,我在循環(huán)的時候,是不是每次都得判斷一下學(xué)生所在的學(xué)校的名稱,然后看是否要給它添加到這個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)]}
*/
有些時候這真的是十分有用且方便的。
但是有時候我們往往不止于如此,假如我要統(tǒng)計每個學(xué)校中20歲年齡以上和20以下的學(xué)生分別有哪些學(xué)生,那么我的參數(shù)就不再是Student::getSchool了,而是要加上語句了。那么該如何編寫呢?
//統(tǒng)計每個學(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)計每個學(xué)校有多少20歲以上和20歲以下的學(xué)生的信息,其實也就是把 return 語句修改以下即可。
//統(tǒng)計每個學(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);
相信大家也看出來groupingBy中的 return 語句就是 Map 中的key值
2、多級分組
但其實groupingBy()并不只是一個人,它也有兄弟姐妹

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

