Java 8 Stream(建议收藏)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观

在 Java 8 的版本更新中,引入的 Java 8 Stream 功能彻底改变了集合数据的处理方式。对于编程初学者和中级开发者来说,这是一个既强大又容易产生困惑的工具。本文将通过循序渐进的方式,结合实际案例,深入讲解 Java 8 Stream 的核心概念、操作方法以及常见问题,帮助读者快速掌握这一工具并将其应用于实际开发中。


什么是 Java 8 Stream?

Java 8 Stream(简称 Stream)是 Java 8 引入的一个用于集合数据处理的抽象模型。它可以将集合中的数据视为一个“数据流”,通过链式调用一系列操作(如过滤、映射、聚合等),高效地完成数据处理任务。

核心思想:流水线式处理

想象一个工厂的流水线:原材料经过多个加工步骤(如切割、打磨、包装),最终产出成品。Stream 的处理流程与此类似

  1. 数据源:如集合(List、Set)或数组。
  2. 中间操作:对数据流进行加工(如筛选、转换)。
  3. 终端操作:触发计算并返回结果(如求和、收集到新集合)。

这种流水线设计的优势在于:

  • 惰性求值:中间操作不会立即执行,而是等到终端操作时才开始计算。
  • 函数式编程风格:通过组合操作链,代码更简洁易读。

Stream 的基本操作分类

Stream 的操作分为两类:中间操作终端操作,它们的组合构成了完整的数据处理流程。

中间操作(Intermediate Operations)

特点:

  • 返回一个新的 Stream 对象,不触发实际计算
  • 支持链式调用,可多次复用(如 filter().map().filter())。
  • 惰性求值:只有在终端操作时才执行所有操作。

常用中间操作示例:

操作方法功能描述
filter()过滤符合条件的元素
map()将元素转换为其他形式
sorted()对元素进行排序
distinct()去重

比喻:流水线的加工步骤

假设我们有一个学生列表,想筛选出成绩大于 80 分的学生,并转换为姓名列表:

List<Student> students = ...  
Stream<Student> filtered = students.stream()  
    .filter(s -> s.getScore() > 80)  // 过滤步骤  
    .map(Student::getName);          // 转换步骤  

此时 filtered 只是定义了操作链,并未真正处理数据。


终端操作(Terminal Operations)

特点:

  • 触发实际计算,返回结果或副作用(如打印输出)。
  • 不可重复调用:每个 Stream 对象只能执行一次终端操作。

常用终端操作示例:

操作方法功能描述
forEach()遍历并执行操作(如打印)
collect()将结果收集到集合中
reduce()聚合操作(如求和、求最大值)
count()统计元素数量

示例:完成流水线并获取结果

List<String> names = students.stream()  
    .filter(s -> s.getScore() > 80)  
    .map(Student::getName)  
    .collect(Collectors.toList()); // 终端操作:收集为 List  

Stream 的核心操作详解

1. 过滤数据:filter()

filter() 通过一个 Predicate(谓词)筛选元素。例如:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);  
List<Integer> evenNumbers = numbers.stream()  
    .filter(n -> n % 2 == 0) // 筛选偶数  
    .collect(Collectors.toList());  

2. 数据转换:map()

map() 将每个元素转换为另一种类型。例如:

List<String> names = students.stream()  
    .map(Student::getName) // 将 Student 对象转为 String  
    .collect(Collectors.toList());  

3. 数据聚合:reduce()

reduce() 是一个强大的聚合操作,支持三元形式:

Optional<Integer> sum = numbers.stream()  
    .reduce(0, (a, b) -> a + b); // 初始值 0,累加所有元素  
  • 初始值:聚合的起点(如求和时初始值为 0)。
  • 二元操作:每次将当前结果与新元素合并。

Stream 的常见错误与解决方案

错误 1:忘记终端操作

students.stream().filter(s -> s.getScore() > 80); // 未触发计算!  

解决方案:添加终端操作:

students.stream().filter(...).forEach(System.out::println);  

错误 2:多次调用终端操作

Stream<Student> stream = students.stream();  
stream.count(); // 第一次调用  
stream.forEach(...); // 抛出 IllegalStateException  

原因:Stream 对象只能使用一次。
解决方案:重新创建 Stream 或提前缓存结果。

错误 3:忽略惰性求值

Stream<Student> filtered = students.stream().filter(...);  
// 在此处修改 students 集合,可能导致不可预测的结果  

注意:Stream 的计算基于原始数据,若数据动态变化,需重新创建 Stream。


实战案例:学生平均分统计

假设我们需要统计所有成绩大于 70 分的学生的平均分:

步骤 1:定义数据

List<Student> students = Arrays.asList(  
    new Student("Alice", 85),  
    new Student("Bob", 65),  
    new Student("Charlie", 90)  
);  

步骤 2:构建 Stream 流程

double average = students.stream()  
    .filter(s -> s.getScore() > 70) // 过滤及格学生  
    .mapToDouble(Student::getScore) // 转为 Double Stream  
    .average() // 终端操作:计算平均值  
    .orElse(0.0); // 处理可能的空值  

输出结果

System.out.println("平均分:" + average); // 输出 87.5  

结论

Java 8 Stream 通过函数式编程的思想,为集合处理提供了优雅且高效的方式。通过理解中间操作与终端操作的协作、熟悉核心方法(如 filter()map()reduce()),开发者可以快速编写出简洁且易维护的代码。

对于初学者,建议从简单案例入手(如过滤、统计),逐步尝试更复杂的操作(如分组、连接流)。同时,需注意 Stream 的惰性求值特性及常见错误,避免因误解导致的逻辑问题。掌握 Stream 后,开发者不仅能提升代码质量,还能更好地适应 Java 生态系统的函数式编程趋势。

通过本文的讲解与案例,希望读者能对 Java 8 Stream 有更全面的认识,并在实际项目中灵活运用这一强大工具。

最新发布