Java Stream API 全面指南(完整版)

embedded/2025/3/26 15:52:42/

Java Stream API 全面指南(完整版)


一、基础概念

1.1 什么是 Stream API?

  • 定义:Java 8 引入的 java.util.stream 包,提供函数式编程风格的流式操作,用于高效处理集合、数组等数据源。
  • 核心特性
    • 惰性计算:中间操作不会立即执行,直到终端操作触发。
    • 无存储:Stream 本身不存储数据,而是从数据源(如集合、数组)获取数据。
    • 不可复用:终端操作执行后,流对象失效,无法再次使用。
    • 函数式:操作不修改源数据,而是返回新流。

1.2 Stream 与 Collection 的区别

特性StreamCollection
数据存储不存储数据,仅作为计算管道存储数据的内存结构
操作效果不修改源数据,返回新流直接修改数据结构
执行时机延迟执行(终端操作触发)立即执行
可复用性一次消费,终端操作后失效可多次遍历

二、Stream 的创建方式

2.1 常见创建方法

从集合创建
java">List<String> list = Arrays.asList("JAVA", "J2EE", "Spring");
// 返回顺序流(Stream<String>)
Stream<String> stream = list.stream();  
// 返回并行流(ParallelStream<String>)
Stream<String> parallelStream = list.parallelStream();
从数组创建
java">int[] array = {1, 2, 3, 4};
// 返回 IntStream(基本类型流)
IntStream intStream = Arrays.stream(array);  
其他数据源
java">// 读取文件行(返回 Stream<String>)
Stream<String> lines = Files.lines(Paths.get("file.txt"));  
// 创建零散数据的流(返回 Stream<String>)
Stream<String> customStream = Stream.of("a", "b", "c");  
// 返回空流(Stream.empty())
Stream<String> emptyStream = Stream.empty();  
双列集合(如 Map)
java">Map<String, Integer> map = new HashMap<>();
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();

三、核心操作

3.1 中间操作(Intermediate Operations)

1. 过滤 filter
java">List<String> filtered = list.stream().filter(s -> s.length() > 3)  // 过滤长度大于3的字符串.collect(Collectors.toList());  
// 结果:["J2EE", "Spring"](假设原列表是 ["JAVA", "J2EE", "Spring"])
2. 映射 map
java">List<Integer> lengths = list.stream().map(String::length)          // 将字符串转换为长度.collect(Collectors.toList());  
// 结果:[4, 5, 6](对应 "JAVA" (4), "J2EE" (5), "Spring" (6))
3. 排序 sorted
java">List<String> sorted = list.stream().sorted()                     // 自然排序(按字母顺序).collect(Collectors.toList());  
// 结果:["JAVA", "J2EE", "Spring"](假设原列表已有序)
4. 扁平化 flatMap
java">List<List<Integer>> listOfLists = Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3, 4));
List<Integer> flattened = listOfLists.stream().flatMap(List::stream).collect(Collectors.toList());  
// 结果:[1, 2, 3, 4]
5. 去重 distinct
java">List<Integer> distinct = Arrays.asList(1, 2, 2, 3).stream().distinct()                  // 去重(依赖 equals() 和 hashCode()).collect(Collectors.toList());  
// 结果:[1, 2, 3]

3.2 终端操作(Terminal Operations)

1. 收集结果 collect
java">// 收集为 List(返回 List<String>)
List<String> result = list.stream().collect(Collectors.toList());  
// 分组(返回 Map<Character, List<String>>)
Map<Character, List<String>> grouped = list.stream().collect(Collectors.groupingBy(s -> s.charAt(0)));  
// 结果:{J=[JAVA, J2EE], S=[Spring]}
2. 归约 reduce
java">// 求和(返回 Integer)
int sum = Stream.of(1, 2, 3).reduce(0, (a, b) -> a + b);  
// 结果:6
// 字符串拼接(返回 Optional<String>)
Optional<String> joined = Stream.of("a", "b", "c").reduce((a, b) -> a + b);  
// 结果:Optional["abc"]
3. 遍历 forEach
java">list.stream().forEach(System.out::println);  
// 输出:
// JAVA
// J2EE
// Spring
4. 匹配与统计
4.1 anyMatch:检查是否存在匹配元素
java">// 检查是否有员工薪资 > 6000
boolean hasHighEarner = Emp.getEmployees().stream().anyMatch(emp -> emp.getSalary() > 6000);  
// 结果:true(Charlie 的薪资为 7000)
4.2 allMatch:检查所有元素是否匹配
java">// 检查所有员工薪资是否 > 5000
boolean allHigh = Emp.getEmployees().stream().allMatch(emp -> emp.getSalary() > 5000);  
// 结果:false(Bob 的薪资为 4500)
4.3 noneMatch:检查是否没有元素匹配
java">// 检查是否有员工薪资 < 4000
boolean noLowEarner = Emp.getEmployees().stream().noneMatch(emp -> emp.getSalary() < 4000);  
// 结果:true(所有薪资 ≥ 4500)
4.4 findFirst:获取第一个元素
java">// 按姓名排序后获取第一个员工
Optional<Emp> firstEmp = Emp.getEmployees().stream().sorted(Comparator.comparing(Emp::getName)).findFirst();  
// 结果:Optional[Emp{id=1, name='Alice', salary=6000}]
4.5 findAny:获取任意一个匹配元素
java">// 并行流中获取任意一个薪资 > 5000 的员工
Optional<Emp> anyEmp = Emp.getEmployees().parallelStream().filter(emp -> emp.getSalary() > 5000).findAny();  
// 结果:可能返回 Alice 或 Charlie(并行流结果可能不固定)

四、高级用法与最佳实践

4.1 并行流(Parallel Streams)

java">List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int parallelSum = numbers.parallelStream().mapToInt(Integer::intValue).sum();  
// 结果:15(并行计算)
注意:
  • 线程安全:使用 Collectors.toConcurrentMap 代替 toMap
java">Map<String, Integer> concurrentMap = list.stream().collect(Collectors.toConcurrentMap(s -> s, s -> s.length()));  
// 结果:线程安全的 Map(如 ConcurrentHashMap)

4.2 自定义收集器(Collector)

java">// 自定义收集器:将元素收集到 ArrayList
Collector<String, List<String>, List<String>> customCollector = Collector.of(ArrayList::new,               // 供应商函数(创建初始容器)(list, element) -> list.add(element), // 累加器(添加元素)(list1, list2) -> {           // 组合器(合并两个容器)list1.addAll(list2); return list1; },list -> list                  // 结果转换器(返回最终结果));List<String> collected = list.stream().collect(customCollector);  
// 结果:与原列表相同(假设原列表是 ["JAVA", "J2EE", "Spring"])

五、常见问题与注意事项

5.1 distinct() 的依赖

java">class Person {String name;// 未重写 equals() 和 hashCode() 方法
}
List<Person> people = Arrays.asList(new Person("Alice"), new Person("Alice"));
List<Person> distinctPeople = people.stream().distinct().collect(Collectors.toList());  
// 结果:可能包含重复项(因未重写 equals 和 hashCode)

5.2 并行流线程安全

java">// 错误示例(非线程安全的 Collectors.toMap)
Map<String, Integer> unsafeMap = list.parallelStream().collect(Collectors.toMap(s -> s, s -> s.length()));  
// 可能抛出 ConcurrentModificationException// 正确示例(线程安全)
Map<String, Integer> safeMap = list.parallelStream().collect(Collectors.toConcurrentMap(s -> s, s -> s.length()));  
// 结果:线程安全的 Map(如 ConcurrentHashMap)

六、完整示例代码

6.1 复杂数据处理(使用自定义类)

java">class Emp {int id;String name;double salary;String sex;public Emp(int id, String name, double salary, String sex) {this.id = id;this.name = name;this.salary = salary;this.sex = sex;}// 新增 getSalary 方法public double getSalary() {return salary;}// 静态方法模拟数据public static List<Emp> getEmployees() {return Arrays.asList(new Emp(1, "Alice", 6000, "男"),new Emp(2, "Bob", 4500,"男"),new Emp(3, "Charlie", 7000, "女"));}
}
6.1.1 过滤高薪员工
java">// 过滤薪资 > 5000 的员工,按薪资降序排序
List<Emp> highEarners = Emp.getEmployees().stream().filter(emp -> emp.getSalary() > 5000).sorted((a, b) -> Double.compare(b.getSalary(), a.getSalary())).collect(Collectors.toList());
// 结果:
// [
//   Emp{id=3, name='Charlie', salary=7000, sex="女"},
//   Emp{id=1, name='Alice', salary=6000, sex="男"}
// ]
6.1.2 groupingBy: 分组与统计
java">// 按姓名分组,统计薪资总和、平均值等
Map<String, DoubleSummaryStatistics> salaryStats = Emp.getEmployees().stream().collect(Collectors.groupingBy(Emp::getName,Collectors.summarizingDouble(Emp::getSalary)));
// 结果:
// {
//   "Alice" = {count=1, sum=6000.0, average=6000.0, min=6000.0, max=6000.0},
//   "Bob" = {count=1, sum=4500.0, average=4500.0, min=4500.0, max=4500.0},
//   "Charlie" = {count=1, sum=7000.0, average=7000.0, min=7000.0, max=7000.0}
// }
java">p -> emp.getSalary() > 6000);  
// 结果:true(Charlie 的薪资为 7000)
6.1.3 partitioningBy:按条件分区

Collectors.partitioningBy() 根据布尔条件将流元素分为两个组,返回 Map<Boolean, List<T>>

示例:按薪资是否高于 6000 分区

java">// 将员工分为薪资 > 6000 和 ≤ 6000 的两组
Map<Boolean, List<Emp>> partitioned = Emp.getEmployees().stream().collect(Collectors.partitioningBy(emp -> emp.getSalary() > 6000));// 结果:
// {
//   true = [Emp{id=3, name='Charlie', salary=7000, sex="女"}],
//   false = [Emp{id=1, name='Alice', salary=6000, sex="男"}, Emp{id=2, name='Bob', salary=4500, sex="男"}]
// }
6.1.4 嵌套分区与分组(如按薪资是否高于 6000,再按性别分组):
java">// 先按薪资分区,再按性别分组
Map<Boolean, Map<String, List<Emp>>> partitionedAndGrouped = Emp.getEmployees().stream().collect(Collectors.partitioningBy(emp -> emp.getSalary() > 6000,Collectors.groupingBy(Emp::getSex)));// 结果:
// {
//   true = {Charlie=[Emp{id=3, name='Charlie', salary=7000, sex="女"}]},
//   false = {Alice=[...], Bob=[...]}
// }

七、总结

7.1 核心优势

  • 简化操作:通过链式调用减少代码量。
  • 高效处理:支持并行计算(如大数据场景)。
  • 函数式编程:避免副作用,代码更易维护。

7.2 注意事项

  • 线程安全:并行流需使用线程安全的收集器。
  • 对象规范:使用 distinct() 时必须重写 equals()hashCode()
  • 不可复用性:终端操作后流对象失效,需重新创建。

7.3 分区与分组的区别

特性partitioningBygroupingBy
分组依据布尔条件(返回 true/false)任意键(如 String、Integer 等)
返回结果Map<Boolean, List<T>>Map<K, List<T>>
适用场景二元条件判断(如是否达标)多元分组(如按性别、地区分组)

关键补充说明:

  1. Emp 类的 getSalary 方法:在 Emp 类中新增 getSalary() 方法,确保 Emp::getSalary 可用。
  2. 常用终端操作示例:补充了 anyMatchallMatchnoneMatchfindFirstfindAny 的完整代码和结果说明。
  3. 并行流的不确定性findAny 在并行流中的结果可能因线程执行顺序不同而变化,需注意其非确定性。
  4. . partitioningBy 的核心作用:将流元素按条件分为两组,适用于简单判断场景。
    • 嵌套使用:可结合 groupingBy 实现多级分组(如分区后进一步分组)。
    • 空值处理:即使无元素满足条件,仍会返回空列表(而非 null)。

http://www.ppmy.cn/embedded/176139.html

相关文章

解决Qt信号在构造函数中失效的问题

情景引入&#xff1a;音乐播放器的“幽灵列表”问题 假设你正在开发一个音乐播放器应用&#xff0c;其中有一个功能是用户首次打开应用时&#xff0c;需要从服务器拉取最新的歌曲列表并显示在“本地音乐”页面中。你可能会写出类似这样的代码&#xff1a; // LocalSong 类的构…

用 pytorch 从零开始创建大语言模型(六):对分类进行微调

用 pytorch 从零开始创建大语言模型&#xff08;六&#xff09;&#xff1a;对分类进行微调 6 微调用于分类6.1 微调的不同类别6.2 准备数据集6.3 创建数据加载器6.4 使用预训练权重初始化模型6.5 添加分类头部6.6 计算分类损失和准确率6.7 在监督数据上微调模型6.8 使用LLM进…

2025年Postman的五大替代工具

虽然Postman是一个广泛使用的API测试工具&#xff0c;但许多用户在使用过程中会遇到各种限制和不便。因此&#xff0c;可能需要探索替代解决方案。本文介绍了10款强大的替代工具&#xff0c;它们能够有效替代Postman&#xff0c;成为你API测试工具箱的一部分。 什么是Postman&…

基于 PyTorch 的 MNIST 手写数字分类模型

一、概述 本代码使用 PyTorch 框架构建了一个简单的神经网络模型&#xff0c;用于解决 MNIST 手写数字分类任务。代码主要包括数据的加载与预处理、神经网络模型的构建、损失函数和优化器的定义、模型的训练、评估以及最终模型的保存等步骤。 二、依赖库 torch&#xff1a;P…

SpringBoot的启动原理?

大家好&#xff0c;我是锋哥。今天分享关于【SpringBoot的启动原理&#xff1f;】面试题。希望对大家有帮助&#xff1b; SpringBoot的启动原理&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Spring Boot的启动原理主要是通过 SpringApplication 类来…

LeetCode 206 Reverse Linked List 反转链表 Java

举例1&#xff1a; 输入&#xff1a; [1,2,3,4,5]&#xff0c; 输出&#xff1a; [5,4,3,2,1]. 举例2&#xff1a; 输入&#xff1a; [] 输出&#xff1a;[] 思路&#xff1a;方法有三种&#xff0c;分别是递归&#xff0c;栈&#xff0c;双指针&#xff0c;本篇使用栈&a…

AI 应用即智能体:探索从 Composer 到 Manus 下的范式演进

简单来说&#xff1a; AI 应用即智能体是将多个 AI 功能模块&#xff08;智能体&#xff09;整合起来&#xff0c;通过服务化的方式&#xff08;如 API&#xff09;提供给 AI&#xff0c;使其能够智能体能够相关交互一样&#xff0c; 利用其它 Agent 的能力来完成各种复杂的任务…

LS-NET-006-思科MDS 9148S 查看内存

LS-NET-006-思科MDS 9148S 查看内存 方法一&#xff1a;使用 show version​ 命令 该命令可显示设备的基本系统信息&#xff0c;包括内存总量。 登录交换机的CLI&#xff08;通过控制台或SSH连接&#xff09;。输入命令&#xff1a; show version 在输出中查找类似以下内容…