Java Stream流之GroupBy的使用方式
Java Stream流之GroupBy的用法
1. 前言
在處理集合數(shù)據(jù)時(shí),我們常常需要將數(shù)據(jù)按照某個(gè)特定條件進(jìn)行分組。例如,在一個(gè)學(xué)生列表中,可能需要按班級(jí)、性別或其他屬性對(duì)學(xué)生進(jìn)行分類統(tǒng)計(jì)。
Java Stream API 提供了強(qiáng)大的功能來實(shí)現(xiàn)這一點(diǎn),其中 group by 是最常用的工具之一。
2. 基礎(chǔ)概念
什么是 GroupBy?
GroupBy 是一種數(shù)據(jù)處理操作,用于根據(jù)指定的條件將數(shù)據(jù)集中的元素分成不同的組。
每組中的元素都共享某個(gè)共同屬性或滿足某個(gè)特定條件。這在數(shù)據(jù)分析、統(tǒng)計(jì)和報(bào)告生成中非常有用。
Stream API 中的 GroupBy
Java 8 引入了 Stream API,它提供了一種高效且簡潔的方式來處理集合數(shù)據(jù)。
group by 是 Stream API 的一部分,允許開發(fā)者輕松地將數(shù)據(jù)分組,并對(duì)每個(gè)組執(zhí)行進(jìn)一步的操作。
3. 基本用法
3.1 分組依據(jù)
在使用 group by 時(shí),首先需要確定根據(jù)什么條件進(jìn)行分組。
這通常是一個(gè)函數(shù),它從每個(gè)元素中提取一個(gè)鍵值(如某個(gè)屬性的值),并根據(jù)這個(gè)鍵值將元素分成不同的組。
示例:按班級(jí)分組
假設(shè)我們有一個(gè)學(xué)生列表:
List<Student> students = Arrays.asList(
new Student("Alice", 20, "Class A"),
new Student("Bob", 21, "Class B"),
new Student("Charlie", 20, "Class A"),
new Student("David", 22, "Class C")
);我們希望將這些學(xué)生按班級(jí)分組。每個(gè)學(xué)生的 className 屬性將作為分組的依據(jù)。
3.2 使用 group by 進(jìn)行分組
在 Stream API 中,使用 Collectors.groupingBy() 方法來實(shí)現(xiàn)分組操作。
該方法需要一個(gè) Classifier 函數(shù),用于從每個(gè)元素中提取分組鍵。
示例代碼:
Map<String, List<Student>> groupedStudents = students.stream()
.collect(Collectors.groupingBy(student -> student.getClassName()));解釋:
students.stream():將學(xué)生列表轉(zhuǎn)換為一個(gè) Stream。.collect(Collectors.groupingBy(...)):使用Collectors.groupingBy()方法進(jìn)行分組。括號(hào)內(nèi)是一個(gè) Lambda 表達(dá)式,用于從每個(gè)學(xué)生對(duì)象中提取className作為分組鍵。- 返回值:得到一個(gè)
Map<String, List<Student>>,其中鍵是班級(jí)名稱(如 “Class A”、“Class B” 等),值是屬于該班級(jí)的學(xué)生列表。
3.3 分組后的操作
一旦數(shù)據(jù)被分組,可以對(duì)每個(gè)組執(zhí)行各種操作,比如統(tǒng)計(jì)組內(nèi)元素的數(shù)量、計(jì)算平均值等。
這通常通過 Collectors 中的其他方法來實(shí)現(xiàn)。
示例:按班級(jí)統(tǒng)計(jì)學(xué)生人數(shù)
Map<String, Long> classCount = students.stream()
.collect(Collectors.groupingBy(
Student::getClassName,
Collectors.counting()
));解釋:
Student::getClassName:使用方法引用作為分組鍵提取函數(shù)。Collectors.counting():指定在每個(gè)組內(nèi)統(tǒng)計(jì)元素的數(shù)量。
結(jié)果:
得到一個(gè) Map<String, Long>,其中鍵是班級(jí)名稱,值是該班級(jí)的學(xué)生人數(shù)。例如:
{
"Class A": 2,
"Class B": 1,
"Class C": 1
}4. 高級(jí)用法
4.1 自定義分組邏輯
在某些情況下,可能需要更復(fù)雜的分組條件。
例如,除了按班級(jí)分組外,還可以根據(jù)年齡區(qū)間對(duì)學(xué)生進(jìn)行分組。
示例:按年齡區(qū)間分組
假設(shè)我們希望將學(xué)生按照年齡段(如 “Under 20”、“20-22”、“Over 22”)進(jìn)行分組。
Map<String, List<Student>> ageGroupedStudents = students.stream()
.collect(Collectors.groupingBy(student -> {
if (student.getAge() < 20) {
return "Under 20";
} else if (student.getAge() <= 22) {
return "20-22";
} else {
return "Over 22";
}
}));解釋:
- Lambda 表達(dá)式:定義了一個(gè)自定義的分組邏輯,根據(jù)學(xué)生的年齡返回不同的區(qū)間字符串。
- 結(jié)果:得到一個(gè)
Map<String, List<Student>>,其中鍵是年齡區(qū)間,值是屬于該區(qū)間的學(xué)生成績列表。
4.2 多級(jí)分組
有時(shí)候需要按照多個(gè)條件進(jìn)行分組。
例如,首先按班級(jí)分組,然后在每個(gè)班級(jí)內(nèi)再按性別分組。這可以通過嵌套 Collectors.groupingBy() 方法來實(shí)現(xiàn)。
示例:按班級(jí)和性別分組
Map<String, Map<String, List<Student>>> groupedByClassAndGender = students.stream()
.collect(Collectors.groupingBy(
Student::getClassName,
Collectors.groupingBy(student -> student.getGender())
));解釋:
- 外層
groupingBy:按班級(jí)分組。 - 內(nèi)層
groupingBy:在每個(gè)班級(jí)內(nèi),再按性別分組。
結(jié)果結(jié)構(gòu):
{
"Class A": {
"Male": [...],
"Female": [...]
},
"Class B": {
"Male": [...],
...
},
...
}4.3 統(tǒng)計(jì)和聚合操作
除了分組之外,還可以對(duì)每個(gè)組內(nèi)的數(shù)據(jù)進(jìn)行統(tǒng)計(jì)和聚合。例如,計(jì)算每個(gè)班級(jí)的平均年齡。
示例:按班級(jí)計(jì)算平均年齡
Map<String, Double> averageAgeByClass = students.stream()
.collect(Collectors.groupingBy(
Student::getClassName,
Collectors.averagingInt(Student::getAge)
));解釋:
Collectors.averagingInt():用于計(jì)算每個(gè)組內(nèi)某個(gè)整數(shù)屬性的平均值。- 結(jié)果:得到一個(gè)
Map<String, Double>,其中鍵是班級(jí)名稱,值是該班級(jí)學(xué)生的平均年齡。
5. 常見應(yīng)用場(chǎng)景
5.1 統(tǒng)計(jì)訂單數(shù)量按地區(qū)分組
假設(shè)有一個(gè)電子商務(wù)平臺(tái),需要統(tǒng)計(jì)每個(gè)地區(qū)的訂單數(shù)量。
List<Order> orders = ...; // 訂單列表
Map<String, Long> orderCountByRegion = orders.stream()
.collect(Collectors.groupingBy(
Order::getRegion,
Collectors.counting()
));5.2 按產(chǎn)品類別計(jì)算銷售額
需要統(tǒng)計(jì)每個(gè)產(chǎn)品類別的總銷售額。
List<ProductSale> sales = ...; // 銷售記錄列表
Map<String, Double> totalSalesByCategory = sales.stream()
.collect(Collectors.groupingBy(
ProductSale::getCategory,
Collectors.summingDouble(ProductSale::getAmount)
));5.3 分析用戶行為按時(shí)間段分組
需要分析網(wǎng)站用戶的訪問時(shí)間分布。
List<UserVisit> visits = ...; // 用戶訪問記錄列表
Map<String, List<UserVisit>> visitsByTimeSlot = visits.stream()
.collect(Collectors.groupingBy(visit -> {
LocalTime time = visit.getVisitTime();
if (time.isBefore(LocalTime.of(12, 0))) {
return "Morning";
} else if (time.isBefore(LocalTime.of(18, 0))) {
return "Afternoon";
} else {
return "Evening";
}
}));6. 注意事項(xiàng)
6.1 空值處理
如果某些元素的分組鍵為 null,默認(rèn)情況下會(huì)將它們放在一個(gè)特殊的 "null" 鍵對(duì)應(yīng)的列表中。
為了避免這種情況或進(jìn)行特殊處理,可以在分組時(shí)提供自定義的空值處理邏輯。
示例:處理 null 分組鍵
Map<String, List<Student>> groupedStudents = students.stream()
.collect(Collectors.groupingBy(
student -> {
String className = student.getClassName();
return className != null ? className : "Unknown Class";
}
));6.2 性能考慮
對(duì)于大數(shù)據(jù)集,分組操作可能會(huì)消耗較多的內(nèi)存和計(jì)算資源。因此,在處理大規(guī)模數(shù)據(jù)時(shí),需要注意性能優(yōu)化。
- 避免復(fù)雜的分組邏輯:盡量使用簡單、高效的分組鍵提取函數(shù)。
- 并行流:如果硬件支持,可以考慮將 Stream 轉(zhuǎn)換為并行流以提高處理速度。例如:
Map<String, List<Student>> groupedStudents = students.parallelStream()
.collect(Collectors.groupingBy(student -> student.getClassName()));7. 總結(jié)
通過本教程的學(xué)習(xí),您應(yīng)該掌握了如何在 Java 中使用 Stream API 的 group by 方法對(duì)數(shù)據(jù)進(jìn)行分組和統(tǒng)計(jì)。無論是在簡單的分類還是復(fù)雜的多級(jí)分組場(chǎng)景中,Stream API 都能提供高效且簡潔的解決方案。
希望這些知識(shí)能夠幫助您在實(shí)際開發(fā)中更好地處理數(shù)據(jù)分組需求!
繼續(xù)深入學(xué)習(xí)?
如果您想進(jìn)一步提高自己的 Java 技能,可以考慮學(xué)習(xí)以下內(nèi)容:
- Java 8+ 新特性:掌握 Lambda 表達(dá)式、函數(shù)式接口等。
- 流操作高級(jí)技巧:了解
Collectors的各種用法和性能優(yōu)化方法。 - 數(shù)據(jù)處理框架:如 Apache Flink、Spark 等,用于處理更大規(guī)模的數(shù)據(jù)。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
spring mvc中的@PathVariable獲得請(qǐng)求url中的動(dòng)態(tài)參數(shù)
本文主要介紹了spring mvc中的@PathVariable獲得請(qǐng)求url中的動(dòng)態(tài)參數(shù)的代碼。具有很好的參考價(jià)值,下面跟著小編一起來看下吧2017-02-02
Spring Data Redis對(duì)象緩存序列化問題解決
相信在項(xiàng)目中,你一定是經(jīng)常使用Redis,在使用時(shí),有沒有遇到同我一樣,對(duì)象緩存序列化問題的呢,本文主要介紹了Spring Data Redis對(duì)象緩存序列化問題解決,感興趣的可以了解一下2024-01-01
SpringMVC中的ConversionServiceExposingInterceptor工具類解析
這篇文章主要介紹了SpringMVC中的ConversionServiceExposingInterceptor工具類解析,ConversionServiceExposingInterceptor是Spring MVC的一個(gè)HandlerInterceptor,用于向請(qǐng)求添加一個(gè)屬性,需要的朋友可以參考下2023-12-12
Spring Boot如何使用httpcomponents實(shí)現(xiàn)http請(qǐng)求
這篇文章主要介紹了Spring Boot使用httpcomponents實(shí)現(xiàn)http請(qǐng)求的示例代碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07
MySQL text類型對(duì)應(yīng)mybatis jdbcType類型方式
這篇文章主要介紹了MySQL text類型對(duì)應(yīng)mybatis jdbcType類型方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07
IDEA?code?template配置和參數(shù)方式
這篇文章主要介紹了IDEA?code?template配置和參數(shù)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教<BR>2024-01-01
SpringBoot整合(ES)ElasticSearch7.8實(shí)踐
本文詳細(xì)介紹了SpringBoot整合ElasticSearch7.8的教程,涵蓋依賴添加、客戶端初始化、索引創(chuàng)建與獲取、批量插入數(shù)據(jù)等核心步驟,結(jié)合官方文檔指引及Lombok簡化實(shí)體類配置,提供完整實(shí)踐方案2025-07-07
Java并發(fā)容器之ConcurrentLinkedQueue詳解
這篇文章主要介紹了Java并發(fā)容器之ConcurrentLinkedQueue詳解,加鎖隊(duì)列的實(shí)現(xiàn)較為簡單,這里就略過,我們來重點(diǎn)來解讀一下非阻塞隊(duì)列,2023-12-12
從點(diǎn)到面, 下面我們來看下非阻塞隊(duì)列經(jīng)典實(shí)現(xiàn)類ConcurrentLinkedQueue,需要的朋友可以參考下

