在 Java 8 之后,Stream API 成为了现代 Java 编程的灵魂之一。
它不仅让代码更简洁、更具函数式风格,还能显著提高开发效率与可读性。
本文将带你从入门到进阶,掌握 Stream 的核心思想与实战技巧。


一、Stream 是什么?

Stream 是 Java 8 引入的新特性,用于对 集合(Collection)数组(Array) 等数据源进行高效的数据处理。
它提供了一种 声明式(Declarative) 编程方式,让我们像写 SQL 一样描述“要做什么”,而不是“怎么做”。

Stream ≠ Collection
Stream 是对数据的一种“视图”或“流水线处理模型”,不是存储数据的容器。


⚙️ 二、Stream 处理流程图

数据源 Collection / Array
创建 Stream
中间操作
filter, map, sorted...
终止操作
forEach, collect, count...
结果输出 / 聚合结果

🌊 三、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 聚合流程

Employee List
groupingBy 部门
averagingDouble 薪资
Map<部门, 平均工资>

⚡ 八、并行流(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);

🏁 十一、总结与建议

优点 缺点
代码简洁、可读性高 调试相对困难
支持并行计算 过度使用会降低性能
适合数据转换与聚合 不适合含大量副作用逻辑的任务

✨ 十二、学习路线推荐

Collection 基础
Lambda 表达式
Stream 基础 API
Collectors 聚合
并行流 ParallelStream
性能优化与调试技巧
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐