272. Java Stream API - 使用数字专用流,避免装箱开销

272. Java Stream API - 使用数字专用流,避免装箱开销

我们知道,Java 中的 Stream<T> 处理的是对象,但如果你处理的是大量的数字(intlongdouble),就需要注意“装箱与拆箱(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,不会立刻执行 执行在终端操作触发时才发生
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容