272. Java Stream API - 使用数字专用流,避免装箱开销
我们知道,Java 中的 Stream<T> 处理的是对象,但如果你处理的是大量的数字(int、long、double),就需要注意“装箱与拆箱(boxing/unboxing)”的问题。
🚫 问题:普通 Stream 会引发装箱性能问题
Stream<Integer> numbers = List.of(1, 2, 3, 4).stream(); // 每个 int 都是 Integer 对象
int sum = numbers.reduce(0, Integer::sum); // 多次装箱与拆箱,性能不佳
✅ 解决方案:使用数字专用流
Java 提供了三个专用数字流接口来避免装箱开销:
-
IntStream:处理int值流 -
LongStream:处理long值流 -
DoubleStream:处理double值流
它们使用的是原始类型(primitive types),不会自动装箱,性能更高。
📊 IntStream 示例:终端操作更丰富
import java.util.stream.IntStream;
public class StatExample {
public static void main(String[] args) {
IntStream stream = IntStream.of(10, 20, 30, 40, 50);
int sum = stream.sum(); // 🚀 无装箱,高效计算
System.out.println("Sum: " + sum); // 输出:Sum: 150
}
}
数字流接口提供了一些 Stream<T> 没有的终端操作:
| 方法名 | 描述 |
|---|---|
sum() |
计算总和 |
min(), max()
|
查找最小值和最大值 |
average() |
计算平均值(返回 OptionalDouble) |
summaryStatistics() |
返回一个统计对象:总数、最小、最大、平均、总和 |
🧮 summaryStatistics() 示例
IntSummaryStatistics stats = IntStream.of(10, 20, 30, 40)
.summaryStatistics();
System.out.println("Count: " + stats.getCount());
System.out.println("Sum: " + stats.getSum());
System.out.println("Average: " + stats.getAverage());
System.out.println("Min: " + stats.getMin());
System.out.println("Max: " + stats.getMax());
输出:
Count: 4
Sum: 100
Average: 25.0
Min: 10
Max: 40
🎯 只需遍历一次数据流,五个指标一次到位!
✅ 最佳实践:使用 Stream 的注意事项
使用 Stream 时,以下行为要避免或注意,以确保安全性、可读性和性能。
⚠️ 一、不要复用 Stream 对象
错误示例:
Stream<String> stream = List.of("A", "B", "C").stream();
var upper = stream.map(String::toUpperCase);
var list = stream.toList(); // ❌ 第二次使用同一个 stream,会抛异常!
正确示例:
List<String> data = List.of("A", "B", "C");
var upper = data.stream().map(String::toUpperCase).toList();
var lower = data.stream().map(String::toLowerCase).toList(); // ✅ 每次新建 stream
📌 流只能使用一次,操作完就“关闭”了。
⚠️ 二、不要把 Stream 存成字段或变量长期保留
为什么?
Stream 是连接到数据源的“流水线”,并不存储数据。如果保存到字段中,容易:
- 被重复使用引发错误
- 引入副作用
- 增加代码难以追踪的数据流动问题
✅ 最佳实践:流应就地创建、就地消费!
// ✅ 好做法
List<Integer> result = List.of(1, 2, 3).stream()
.map(i -> i * 2)
.filter(i -> i > 3)
.toList();
⚠️ 三、避免对流外部变量产生副作用
int[] total = {0};
List.of(1, 2, 3).stream().forEach(i -> total[0] += i); // ❌ 修改外部变量,副作用!
System.out.println(total[0]);
这段代码虽然能运行,但它:
- 破坏了函数式编程的纯粹性
- 在并行流中(parallel stream)可能导致竞态条件(race condition)
✅ 推荐写法:
int sum = List.of(1, 2, 3).stream()
.mapToInt(Integer::intValue)
.sum(); // ✅ 无副作用
✅ 总结小贴士
| 规则 | 原因 |
|---|---|
| ✔ 使用 IntStream、LongStream、DoubleStream | 避免装箱,提高数字处理性能 |
| ✔ 每次新建 stream,不要重复使用同一个 | Stream 一次性消费,不能重复使用 |
| ✔ 不要把 stream 存成字段或变量长期保留 | 增加代码复杂性,并可能导致使用错误 |
| ✔ 避免修改外部变量(无副作用) | 保持函数式风格,保证线程安全 |
| ✔ 中间操作返回新 stream,不会立刻执行 | 执行在终端操作触发时才发生 |