在 Java 8 之后,Stream API 成为了现代 Java 编程的灵魂之一。
它不仅让代码更简洁、更具函数式风格,还能显著提高开发效率与可读性。
本文将带你从入门到进阶,掌握 Stream 的核心思想与实战技巧。
一、Stream 是什么?
Stream 是 Java 8 引入的新特性,用于对 集合(Collection) 或 数组(Array) 等数据源进行高效的数据处理。
它提供了一种 声明式(Declarative) 编程方式,让我们像写 SQL 一样描述“要做什么”,而不是“怎么做”。
Stream ≠ Collection
Stream 是对数据的一种“视图”或“流水线处理模型”,不是存储数据的容器。
⚙️ 二、Stream 处理流程图
🌊 三、Stream 的核心操作类型
| 类型 | 说明 | 示例 |
|---|---|---|
| 创建(Create) | 从集合、数组、文件、函数等创建流 | list.stream() |
| 中间操作(Intermediate) | 转换流、过滤元素、排序等,返回新的流 | filter(), map(), sorted() |
| 终止操作(Terminal) | 触发计算并产生结果 | collect(), forEach(), count() |
🧩 四、传统写法 vs Stream 写法对比
💡 案例 1:筛选并统计年龄大于 25 的员工数量
🏷 传统写法
List<Employee> employees = ...;
int count = 0;
for (Employee e : employees) {
if (e.getAge() > 25) {
count++;
}
}
System.out.println(count);
🚀 Stream 写法
long count = employees.stream()
.filter(e -> e.getAge() > 25)
.count();
System.out.println(count);
✅ 对比优势:
- 传统写法:命令式、可读性差
- Stream:声明式、简洁、线程安全(可配合并行流)
💡 案例 2:获取姓名列表(去重 + 排序)
传统写法
List<String> names = new ArrayList<>();
for (Employee e : employees) {
if (!names.contains(e.getName())) {
names.add(e.getName());
}
}
Collections.sort(names);
Stream 写法
List<String> names = employees.stream()
.map(Employee::getName)
.distinct()
.sorted()
.toList();
✅ 优势总结
| 方面 | 传统写法 | Stream 写法 |
|---|---|---|
| 可读性 | 低,逻辑冗长 | 高,清晰表达意图 |
| 可维护性 | 修改代价大 | 可自由组合操作 |
| 性能 | 顺序执行 | 支持并行流 parallelStream() |
🔁 五、常用中间操作详解
| 方法 | 功能 | 示例 |
|---|---|---|
filter(Predicate) | 按条件筛选 | .filter(e -> e.getAge() > 30) |
map(Function) | 转换元素 | .map(Employee::getName) |
flatMap(Function) | 扁平化嵌套集合 | .flatMap(List::stream) |
distinct() | 去重 | .distinct() |
sorted() | 排序 | .sorted(Comparator.comparing(Employee::getAge)) |
limit(n) / skip(n) | 截取 / 跳过元素 | .limit(10) |
🧮 六、常用终止操作详解
| 方法 | 功能 | 示例 |
|---|---|---|
forEach() | 遍历流元素 | .forEach(System.out::println) |
collect() | 收集结果为集合或Map | .collect(Collectors.toList()) |
count() | 统计数量 | .count() |
reduce() | 聚合计算 | .reduce(0, Integer::sum) |
anyMatch() / allMatch() / noneMatch() | 条件匹配 | .anyMatch(e -> e.getAge() > 40) |
findFirst() / findAny() | 查找元素 | .findFirst().orElse(null) |
🔍 七、进阶案例:数据聚合与分组
示例:按部门统计平均工资
Map<String, Double> avgSalary = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.averagingDouble(Employee::getSalary)
));
输出:
{开发部=15000.0, 测试部=12000.0, 产品部=18000.0}
图示:Stream 聚合流程
⚡ 八、并行流(Parallel Stream)
只需将 stream() 改为 parallelStream() 即可自动并行化处理:
double sum = employees.parallelStream()
.filter(e -> e.getAge() > 25)
.mapToDouble(Employee::getSalary)
.sum();
⚠️ 注意:并行流在数据量小或线程安全性要求高的场景下 反而可能性能更差。
🧠 九、Stream 使用技巧与陷阱
| 场景 | 建议 |
|---|---|
| 频繁创建流 | 不要在循环中重复 .stream() |
| 修改外部变量 | 避免副作用操作,如在 forEach 中修改外部集合 |
| 空集合 | 推荐 Collections.emptyList().stream() 防止 NPE |
| 调试输出 | 使用 .peek(System.out::println) 查看中间状态 |
🧩 十、完整实战:员工数据分析示例
List<Employee> employees = List.of(
new Employee("张三", "开发部", 28, 15000),
new Employee("李四", "开发部", 35, 18000),
new Employee("王五", "测试部", 24, 12000),
new Employee("赵六", "产品部", 30, 20000)
);
// 1. 获取30岁以上员工姓名
List<String> result = employees.stream()
.filter(e -> e.getAge() > 30)
.map(Employee::getName)
.toList();
// 2. 部门平均工资
Map<String, Double> avgSalary = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.averagingDouble(Employee::getSalary)
));
// 3. 工资最高的员工
Employee top = employees.stream()
.max(Comparator.comparing(Employee::getSalary))
.orElse(null);
🏁 十一、总结与建议
| 优点 | 缺点 |
|---|---|
| 代码简洁、可读性高 | 调试相对困难 |
| 支持并行计算 | 过度使用会降低性能 |
| 适合数据转换与聚合 | 不适合含大量副作用逻辑的任务 |

941

被折叠的 条评论
为什么被折叠?



