Java Stream API 完全指南
目录
- Stream 核心概念
- 中间操作(Intermediate Operations)
- 终端操作(Terminal Operations)
- 并行流与性能优化
- 使用场景与最佳实践
- 常见问题与陷阱
1. Stream 核心概念
1.1 什么是 Stream?
- 定义:表示元素序列的抽象数据管道,支持函数式编程风格的操作。
- 特性:
- 不存储数据:仅传递数据源(集合、数组、I/O)的元素。
- 不可变:操作生成新 Stream,不修改源数据。
- 延迟执行:中间操作在终端操作触发时执行。
- 可消费性:Stream 只能被操作一次。
1.2 创建 Stream
| 方法 | 示例 |
|---|---|
集合:stream() / parallelStream() |
list.stream() |
数组:Arrays.stream() |
Arrays.stream(new int[]{1,2,3}) |
值序列:Stream.of() |
Stream.of("a", "b", "c") |
文件:Files.lines() |
Files.lines(Path.of("file.txt")) |
无限流:generate() / iterate() |
Stream.generate(Math::random).limit(5) |
2. 中间操作(Intermediate Operations)
中间操作返回新 Stream,支持链式调用。以下是完整分类:
2.1 筛选与切片
| 操作 | 描述 | 示例 |
|---|---|---|
filter(Predicate) |
过滤满足条件的元素 | .filter(n -> n % 2 == 0) |
distinct() |
去重(依赖 equals()) |
.distinct() |
limit(n) |
截取前 n 个元素 |
.limit(3) |
skip(n) |
跳过前 n 个元素 |
.skip(2) |
takeWhile(Predicate) (Java 9+) |
取元素直到条件不满足 | .takeWhile(n -> n < 5) |
dropWhile(Predicate) (Java 9+) |
丢弃元素直到条件不满足 | .dropWhile(n -> n < 5) |
2.2 映射与转换
| 操作 | 描述 | 示例 |
|---|---|---|
map(Function) |
元素一对一转换 | .map(s -> s.toUpperCase()) |
flatMap(Function) |
元素一对多转换并扁平化 | .flatMap(s -> Stream.of(s.split(" "))) |
peek(Consumer) |
观察元素(调试用) | .peek(System.out::println) |
2.3 排序与状态
| 操作 | 描述 | 示例 |
|---|---|---|
sorted() |
自然排序 | .sorted() |
sorted(Comparator) |
自定义排序规则 | .sorted(Comparator.reverseOrder()) |
unordered() |
声明无需顺序(优化并行性能) | .unordered() |
3. 终端操作(Terminal Operations)
终端操作触发流处理,返回非流结果。
3.1 搜索与匹配
| 操作 | 描述 | 示例 |
|---|---|---|
anyMatch(Predicate) |
是否存在至少一个元素满足条件 | .anyMatch(s -> s.contains("a")) |
allMatch(Predicate) |
是否所有元素满足条件 | .allMatch(s -> s.length() > 2) |
noneMatch(Predicate) |
是否没有元素满足条件 | .noneMatch(s -> s.startsWith("z")) |
findFirst() |
返回第一个元素(保证顺序) | .findFirst() |
findAny() |
返回任意元素(并行流优化) | .findAny() |
3.2 聚合与收集
| 操作 | 描述 | 示例 |
|---|---|---|
count() |
返回元素总数 | .count() |
collect(Collector) |
转换为集合或其他数据结构 | .collect(Collectors.toList()) |
reduce() |
归约操作(如求和、最大值) | .reduce(0, Integer::sum) |
forEach(Consumer) |
遍历元素(无返回值) | .forEach(System.out::println) |
toArray() |
转换为数组 | .toArray(String[]::new) |
3.3 数值流特化(避免装箱开销)
| 操作 | 描述 | 示例 |
|---|---|---|
sum() |
求和(仅限数值流) | IntStream.of(1,2,3).sum() |
average() |
平均值 | IntStream.range(1,5).average() |
max() / min() |
最大值/最小值 | IntStream.of(1,2,3).max() |
4. 并行流与性能优化
4.1 启用并行流
java
复制
List<Integer> list = Arrays.asList(1, 2, 3);
Stream<Integer> parallelStream = list.parallelStream(); // 集合方式
Stream<Integer> parallel = Stream.of(1,2,3).parallel(); // 显式转换
4.2 并行流注意事项
- 线程安全:确保操作无状态且无副作用。
- 避免有状态中间操作:如
sorted、distinct可能降低性能。 - 短路操作优化:
limit、findFirst在有序流中可能串行更高效。
4.3 性能测试工具
java
复制
long start = System.currentTimeMillis();
// 执行流操作
long duration = System.currentTimeMillis() - start;
System.out.println("耗时:" + duration + "ms");
5. 使用场景与最佳实践
5.1 典型场景
| 场景 | 示例操作链 |
|---|---|
| 数据过滤与转换 | .filter(...).map(...).collect(...) |
| 统计与聚合 | .mapToInt(...).average().getAsDouble() |
| 分页查询 | .skip((page-1)*size).limit(size).collect(...) |
| 条件检查 | .anyMatch(...) 或 .noneMatch(...) |
5.2 最佳实践
- 优先使用方法引用:
String::toUpperCase替代s -> s.toUpperCase()。 - 合并过滤条件:将多个
filter合并为一个复合谓词。 - 避免副作用:不要在
peek或map中修改外部状态。 - 谨慎使用并行流:数据量大且无状态操作时启用。
6. 常见问题与陷阱
6.1 流已被操作或关闭
java
复制
Stream<String> stream = Stream.of("a", "b");
stream.forEach(System.out::println);
stream.count(); // 抛出 IllegalStateException
6.2 无限流未限制
java
复制
Stream.generate(() -> "data").forEach(System.out::println); // 无限循环
6.3 副作用导致的并发问题
java
复制
List<Integer> sharedList = new ArrayList<>();
IntStream.range(0, 1000).parallel().forEach(sharedList::add); // 非线程安全!
6.4 装箱/拆箱开销
java
复制
// 错误:使用 Stream<Integer> 导致频繁装箱
Stream.of(1,2,3).reduce(0, Integer::sum);
// 正确:使用 IntStream
IntStream.of(1,2,3).sum();
附录:常用 Collectors 工具方法
| 方法 | 描述 | 示例 |
|---|---|---|
toList() / toSet() |
转换为 List/Set | .collect(Collectors.toList()) |
toMap(keyMapper, valueMapper) |
转换为 Map | .collect(Collectors.toMap(k -> k, v -> v)) |
joining(delimiter) |
字符串拼接 | .collect(Collectors.joining(", ")) |
groupingBy(classifier) |
按条件分组 | .collect(Collectors.groupingBy(User::getAge)) |
partitioningBy(predicate) |
按布尔条件分区 | .collect(Collectors.partitioningBy(s -> s.length() > 5)) |