Java Stream 流详解
1. 什么是 Stream?
Stream(流) 是 Java 8 引入的一个强大工具,用于简化集合处理操作。
它提供了一种更**声明式(类似 SQL 查询)**的方式处理数据,使代码更简洁可读。
Stream 不是集合,也不存储数据;它是对数据源的一种 操作视图。
2. Stream 的特点
- 不存储数据:Stream 只是"看着"集合的数据做操作。
- 函数式风格:大量使用 lambda 表达式。
- 惰性求值:大多数操作不会立刻执行,只有遇到终止操作才执行。
- 一次性:一个 Stream 只能消费一次。
- 可并行 :通过
parallelStream()自动利用多核 CPU。
3. Stream 的常见操作分类
Stream 操作分为两类:
◆ 中间操作(返回 Stream,本身不执行)
常见中间操作:
| 方法 | 作用 |
|---|---|
filter() |
过滤 |
map() |
映射(转换) |
sorted() |
排序 |
limit() |
限制前 n 个 |
skip() |
跳过前 n 个 |
distinct() |
去重 |
mapToInt() |
转换为 IntStream |
中间操作是惰性的:不遇到终止操作不会真正执行。
◆ 终止操作(触发执行)
常见终止操作:
| 方法 | 作用 |
|---|---|
forEach() |
遍历 |
collect() |
收集成集合 |
count() |
计数 |
findFirst() |
查找第一个 |
findAny() |
查找任意一个(并行时效果好) |
anyMatch() |
是否有任意一个匹配 |
allMatch() |
是否所有都匹配 |
4. Stream 使用流程(三步走)
① 获取 Stream → ② 中间操作 → ③ 终止操作
例子:
List<String> list = Arrays.asList("a", "b", "c");
list.stream()
.filter(s -> s.equals("a")) // 中间操作
.forEach(System.out::println); // 终止操作
5. 获取 Stream 的方式
① 从集合创建
list.stream();
list.parallelStream();
② 从数组创建
Arrays.stream(arr);
③ 从静态方法 Stream.of()
Stream.of(1, 2, 3);
6. 常见使用案例总结
示例1:过滤 filter
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.stream()
.filter(n -> n % 2 == 0)
.forEach(System.out::println);
示例2:映射 map
List<String> list = Arrays.asList("Java", "Python", "Go");
list.stream()
.map(String::toUpperCase)
.forEach(System.out::println);
示例3:排序 sorted
list.stream()
.sorted()
.forEach(System.out::println);
示例4:收集 collect
List<Integer> even = list.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
示例5:去重 distinct
Stream.of(1,2,2,3,3,3)
.distinct()
.forEach(System.out::println);
示例6:统计 count
long count = list.stream().count();
7. Stream 的并行 parallelStream()
非常适合集合数据量大时使用。
list.parallelStream()
.forEach(System.out::println);
自动利用 CPU 多核,提高性能。
8. Stream 和普通 for 的对比
| 对比点 | for 循环 | Stream |
|---|---|---|
| 可读性 | 逐句命令式 | 声明式、高可读 |
| 支持并行 | 手动操作线程 | 自动并行 |
| 转换/过滤 | 自己写代码 | 内置 API |
| Lambda | 不支持 | 完全支持 |
一般在处理 集合流水线操作 时 Stream 更优雅、更安全、更易维护。
9. 注意点
- Stream 只能消费一次
- 中间操作不会立即执行
- 使用 parallelStream() 时注意线程安全
- Stream 不是万能,有些场景普通 for 反而更高效
🚀 一、Stream 只能消费一次是什么意思?
✔ 1. Stream 是"一次性"的流水线
你可以把 Stream 想成:
一次从头到尾的处理流程(Pipeline)。
一旦你执行了终止操作(forEach、collect、count 等),
这个 Stream 就已经"用完"了,不能再用。
就像:
- 水流过水管以后,不能再把同一股水装回去重新流
- 用过的快递面单不能重复用
📌 示例(错误用法)
Stream<String> stream = List.of("a", "b", "c").stream();
stream.forEach(System.out::println);
stream.forEach(System.out::println); // ❌ 会报错
第二次使用会报错:
java.lang.IllegalStateException: stream has already been operated upon or closed
✔ 2. 为什么不能用第二次?
因为:
- Stream 是对集合的一次遍历视图
- 执行终止操作时,它内部的迭代器就被关闭
- 关闭后就不能再遍历
这叫:单次消费(one-shot)。
🚀 二、:: 是什么?(方法引用 Method Reference)
:: 是 Java 8 的语法糖,用于引用方法。
和 Lambda 表达式作用类似,但更简洁。
✔ 1. 最常见的用法:System.out::println
等价于:
x -> System.out.println(x)
也就是:
- "把元素 x 打印出来"
::只是引用了一个已有方法,而不是写新的 lambda
✔ 2. :: 的四种常见形式
| 写法 | 含义 | 等价 lambda |
|---|---|---|
ClassName::staticMethod |
静态方法引用 | x -> ClassName.staticMethod(x) |
object::instanceMethod |
某对象的实例方法 | x -> object.instanceMethod(x) |
ClassName::instanceMethod |
由实例调用的方法 | (obj, x) -> obj.instanceMethod(x) |
ClassName::new |
构造方法引用 | () -> new ClassName() |
✔ 3. 一些常见例子
① 静态方法引用
Stream.of("1", "2", "3")
.map(Integer::parseInt);
等价于:
.map(s -> Integer.parseInt(s))
② 实例方法引用
List<String> list = List.of("a", "b");
list.forEach(System.out::println);
等价于:
list.forEach(x -> System.out.println(x));
③ 构造方法引用
Stream.generate(Person::new);
等价于:
Stream.generate(() -> new Person());
🚀 三、最容易理解的大总结
✔ Stream 只能消费一次
- 执行终止操作后就关闭
- 不能重复遍历
- 必要时重新创建 Stream:
list.stream()
✔ :: 是方法引用
- 用于简化 lambda 表达式
- 常见例子:
System.out::println - 代表"调用这个方法,不用自己写函数体"