【Java8进阶】Stream API 万字精讲:从原理到实战,告别冗余集合代码
【Java8进阶】Stream API 万字精讲:从原理到实战,告别冗余集合代码
阅读须知:本文基于 JDK8 原生特性,无第三方依赖,所有代码可直接复制运行。内容涵盖 Stream 核心原理、创建方式、中间操作、终止操作、分组聚合、并行流及常见坑点,步骤极致详细,适合收藏复盘、面试突击、项目落地。
博客适配:结构分层清晰,代码块标注完整,无冗余废话,兼顾新手易懂、老手深度参考。
一、前言:为什么必须掌握 Stream?
在 Java8 之前,我们操作集合(List/Set/Map)只能通过 for 循环、增强 for、迭代器 的命令式编程方式。这种写法存在两个核心痛点:
- 代码冗余:简单的筛选、排序、统计逻辑,需要写大量模板代码,业务逻辑被循环、判断语法淹没;
- 可读性差:命令式代码关注「怎么做」(循环、遍历、判断),而非「做什么」(筛选数据、分组统计),复杂业务逻辑难以快速读懂。
Java8 引入的 Stream 流式编程,基于函数式编程思想,将集合操作抽象为「流式管道」,通过链式调用完成数据处理,核心优势:
- 代码极简:一行代码完成筛选、转换、排序、分组、统计等复杂操作;
- 逻辑清晰:声明式编程,只关注业务结果,不关注遍历过程;
- 性能可控:支持串行/并行切换,无需手动编写多线程代码;
- 无侵入性:Stream 不会修改原集合,所有操作都会生成新结果,规避数据污染问题。
二、Stream 核心底层原理(必懂)
很多开发者只会用 Stream 语法,不懂底层机制,导致出现 空指针、重复执行、并行异常 等问题。先掌握核心原理,再写代码零踩坑。
2.1 Stream 是什么?
Stream 是 数据管道,不是集合、不是数据结构。它不存储数据,只负责「传输、处理数据」,数据源可以是集合、数组、文件、随机数等。
简单理解:集合是存储数据的容器,Stream 是处理数据的工具。
2.2 Stream 三组件(执行流程)
所有 Stream 代码都遵循固定三段式流程,缺一不可:
- 创建流(Source):从数据源生成 Stream 对象(集合、数组、静态方法等);
- 中间操作(Intermediate):对数据进行筛选、转换、排序、去重等处理,延迟执行,返回新流,支持链式调用;
- 终止操作(Terminal):触发流执行,生成最终结果(集合、数值、布尔值等),执行后流关闭,不可复用。
2.3 核心特性:惰性求值(重点)
Stream 中间操作全部是 惰性执行:没有终止操作,中间操作永远不会执行。
优势:避免无效计算,链式调用会合并所有中间操作,遍历一次数据即可完成所有处理,性能远优于多次 for 循环。
反面案例(无效代码):只写中间操作,无终止操作,代码完全不执行:
Listlist = Arrays.asList(1,2,3,4,5);
// 无终止操作,filter、map 完全不执行
list.stream().filter(x -> x > 2).map(x -> x * 2);
2.4 流的不可复用性
Stream 一旦执行终止操作,流就会被关闭,再次调用会抛出 IllegalStateException 异常。
Streamstream = list.stream().filter(x -> x > 2);
stream.count(); // 第一次终止操作,流关闭
stream.collect(Collectors.toList()); // 报错:流已关闭,不可复用
三、Stream 最全创建方式(全覆盖)
整理项目中所有常用流创建方式,覆盖 99% 业务场景,可直接复用。
3.1 从集合创建(最常用)
所有 Collection 子类(List/Set)自带流方法:
- stream():创建串行流(单线程,默认);
- parallelStream():创建并行流(多线程,大数据量使用)。
// List 创建流
ListnameList = Arrays.asList("张三","李四","王五");
Streamstream = nameList.stream();
StreamparallelStream = nameList.parallelStream();
// Set 创建流
Set
Stream
3.2 从数组创建
通过 Arrays.stream() 工具类创建流,支持基本类型、引用类型数组:
// 引用类型数组
String[] arr = {"Java","Python","Go"};
Stream
// 基本类型数组(专属原始流,避免自动装箱拆箱,性能更高)
int[] intArr = {1,2,3,4};
IntStream intStream = Arrays.stream(intArr);
3.3 通过 Stream 静态方法创建
// 1. 可变参数创建流
Stream
// 2. 创建空流(规避空集合处理空指针)
Stream
// 3. 迭代生成无限流(需配合 limit 截断)
Stream
// 4. 生成随机无限流
Stream
3.4 其他特殊流(文件、路径)
日常业务较少用,适合文件批量处理场景:
// 读取文件每行数据为流
Stream
// 遍历文件路径流
Stream
四、核心中间操作(详细语法+实战案例)
中间操作是 Stream 核心,所有操作链式调用、延迟执行、返回新流。整理项目最常用 8 大操作,附带完整可运行案例。
前置测试实体类(全文通用):统一员工实体,所有案例基于此对象,无需重复定义
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
// 员工实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
// 姓名、年龄、薪资、部门
private String name;
private Integer age;
private Double salary;
private String department;
}
前置测试数据
List
new Employee("张三", 25, 8500.0, "技术部"),
new Employee("李四", 28, 12000.0, "技术部"),
new Employee("王五", 22, 7500.0, "运营部"),
new Employee("赵六", 30, 15000.0, "产品部"),
new Employee("张三", 27, 9000.0, "运营部")
);
4.1 filter:条件筛选(过滤数据)
作用:根据指定条件过滤数据,保留 条件为 true 的元素
参数:Predicate
实战需求:筛选出年龄大于25岁、薪资大于8000的员工
List
.filter(emp -> emp.getAge() > 25 && emp.getSalary() > 8000)
.collect(Collectors.toList());
System.out.println(filterEmp);
// 输出:李四、赵六
4.2 map:数据转换/类型映射
作用:将流中元素 一对一转换,修改元素内容、类型、提取字段
参数:Function<T,R> 转换函数(T入参,R返回值)
实战需求:提取所有员工姓名,转为大写
List
.map(Employee::getName) // 提取姓名字段
.map(String::toUpperCase) // 转为大写
.collect(Collectors.toList());
System.out.println(nameList);
// 输出:[张三, 李四, 王五, 赵六, 张三](大写格式)
4.3 flatMap:扁平化映射(一对多转换)
作用:解决嵌套集合扁平化 问题,将多个子集合/数组拆分为单个元素流
场景:拆分字符串、嵌套集合遍历、多集合合并去重
实战需求:拆分多个字符串,统一收集所有单词
List
List
.map(str -> str.split(" ")) // 拆分字符串,得到数组流 Stream<String[]>
.flatMap(Arrays::stream) // 扁平化:数组流转为元素流 Stream
.collect(Collectors.toList());
System.out.println(wordList);
// 输出:[Java, Stream, Python, Go, Spring, Boot]
4.4 sorted:排序
两种用法:
- sorted():自然排序(元素实现 Comparable 接口);
- sorted(Comparator):自定义比较器排序(支持单字段、多字段、升降序)。
实战需求:按薪资降序排序,薪资相同按年龄升序
ListsortEmp = employeeList.stream()
.sorted(Comparator.comparingDouble(Employee::getSalary).reversed() // 薪资降序
.thenComparingInt(Employee::getAge)) // 薪资相同,年龄升序
.collect(Collectors.toList());
4.5 distinct:去重
原理:基于 equals() 和 hashCode() 去重
注意:自定义实体去重,必须重写 equals 和 hashCode 方法,否则去重失效
实战需求:去除重复姓名的员工
// 方式1:原生去重(整对象去重)
ListdistinctEmp = employeeList.stream()
.distinct()
.collect(Collectors.toList());
// 方式2:按指定字段去重(项目常用,无需重写方法)
List
.filter(distinctByKey(Employee::getName))
.collect(Collectors.toList());
// 自定义去重工具方法(通用)
public static
Set
浙公网安备 33010602011771号