IntStream、LongStream 这些数值流是干什么的?为什么处理数字要用它们?
简化版
**IntStream、LongStream、DoubleStream 是「基本类型的特化流」——它们处理 int/long/double 时直接用基本类型,避免了普通 Stream<Integer> 的「装箱/拆箱」开销,性能更好,还提供了专门的数值方法(sum、average、range、summaryStatistics 等)。**为什么需要它们:普通 Stream<Integer> 里的每个元素是包装类 Integer(对象),做数值运算要不断「拆箱成 int 算、再装箱成 Integer」,大量数据时装箱拆箱开销很大;数值流直接用 int,没有这个开销。常用能力:① 直接求和/平均——sum()、average()(普通 Stream 没有,要 reduce);② 生成范围——IntStream.range(0, 10)(09)、10),替代传统 for 循环;③ 一次性统计——rangeClosed(1, 10)(1summaryStatistics() 返回 IntSummaryStatistics,一次拿到 count/sum/min/max/average;④ 互转——mapToInt(对象流转数值流)、boxed/mapToObj(数值流转对象流)。核心:处理数字用数值流(避免装箱、有专门数值方法),对象流和数值流之间用 mapToInt/boxed 互转。
详细版
三种数值流:
| 流 | 元素类型 |
|---|---|
| IntStream | int |
| LongStream | long |
| DoubleStream | double |
数值流的专属方法:
| 方法 | 作用 |
|---|---|
| sum() | 求和(普通 Stream 没有) |
| average() | 平均(返回 OptionalDouble) |
| max()/min() | 最值(返回 OptionalInt) |
| range(a,b)/rangeClosed(a,b) | 生成范围([a,b) / [a,b]) |
| summaryStatistics() | 一次拿 count/sum/min/max/average |
| mapToObj/boxed | 转成对象流 |
// 求和:数值流直接 sum(普通 Stream 要 reduce)
int total = list.stream().mapToInt(Order::getAmount).sum();
// 平均
OptionalDouble avg = list.stream().mapToInt(Order::getAmount).average();
// 生成范围(替代 for 循环)
IntStream.range(0, 5).forEach(System.out::println); // 0 1 2 3 4
IntStream.rangeClosed(1, 5).forEach(System.out::println); // 1 2 3 4 5
int sum = IntStream.rangeClosed(1, 100).sum(); // 1+2+...+100 = 5050
// 一次拿所有统计值
IntSummaryStatistics stats = list.stream()
.mapToInt(Order::getAmount)
.summaryStatistics();
stats.getCount(); stats.getSum(); stats.getMin();
stats.getMax(); stats.getAverage(); // 一次遍历全拿到
// 互转:对象流 ↔ 数值流
IntStream is = list.stream().mapToInt(Order::getAmount); // 对象流 → IntStream
Stream<Integer> boxed = is.boxed(); // IntStream → Stream<Integer>
Stream<String> strs = IntStream.range(0, 3).mapToObj(i -> "item" + i); // → 对象流
⚠️ 数值流的核心价值是「避免装箱拆箱」——
Stream<Integer>里每个元素是Integer对象(在堆上),对它做数值运算(+、比较)时,JVM 要先「拆箱」把Integer转成int计算、有时还要「装箱」把结果转回Integer。大量数据时,这些装箱/拆箱 + 对象创建的开销累积起来很可观。IntStream直接用int(栈上的基本类型),全程没有装箱/拆箱,也不创建Integer对象,所以处理数字更快、更省内存。所以当你要对一堆数字求和、求平均、统计时,用mapToInt转成IntStream再操作(list.stream().mapToInt(...).sum()),而不是list.stream().map(...).reduce(0, Integer::sum)(后者一路装箱)。另外数值流有专门的sum()/average(),普通Stream没有(要用 reduce/collect,更繁琐)。
完整版教学
一、问题:Stream 的装箱开销
先理解普通对象流处理数字的问题:
Stream<Integer> 处理数字的问题——装箱/拆箱:
Stream<Integer> 里每个元素是 Integer(包装类、堆上的对象)
做数值运算时:
Integer a, b;
a + b → 先把 a、b 拆箱成 int → int 相加 → 结果可能装箱回 Integer
→ 每次运算都有"拆箱(Integer→int)"、有时"装箱(int→Integer)"
大量数据时:
100 万个 Integer 求和:
→ 100 万次拆箱 + 中间结果装箱
→ 装箱/拆箱开销 + Integer 对象占内存 + GC 压力
→ 慢、费内存
Integer 对象本身也占空间:
int 是 4 字节;Integer 是一个对象(对象头 + int 值,十几字节)
→ 100 万个 Integer 比 100 万个 int 占多得多的内存
所以:用 Stream<Integer> 处理大量数字,装箱拆箱是性能负担
→ 需要"直接用 int"的流 → IntStream
普通 Stream<Integer> 处理数字的问题是「装箱/拆箱开销」——每个元素是 Integer(堆上对象),数值运算要拆箱成 int 算、有时装箱回 Integer;大量数据时装箱/拆箱开销 + Integer 对象占内存 + GC 压力(100 万个 Integer 比 100 万个 int 占多得多内存,int 4 字节 vs Integer 对象十几字节)。所以需要「直接用 int」的流——IntStream。理解「Stream
二、IntStream:直接用 int
IntStream 直接用基本类型 int,没有装箱:
IntStream(还有 LongStream/DoubleStream):
元素直接是 int(基本类型,栈上),不是 Integer 对象
→ 全程没有装箱/拆箱
→ 不创建 Integer 对象(省内存、省 GC)
→ 数值运算直接用 int(快)
对比:
Stream<Integer>:元素是 Integer 对象,运算要装箱拆箱
IntStream:元素是 int,运算直接算
所以处理数字,IntStream 比 Stream<Integer> 快、省内存
三种数值流:
IntStream:int
LongStream:long
DoubleStream:double
(没有 ByteStream/ShortStream,用 IntStream 覆盖)
IntStream(及 LongStream/DoubleStream)直接用基本类型(元素是 int 不是 Integer 对象)——全程无装箱/拆箱、不创建对象、运算直接用 int(快、省内存、省 GC)。对比 Stream<Integer>(元素是对象、运算要装箱拆箱),处理数字 IntStream 更快更省内存。三种:IntStream(int)/LongStream(long)/DoubleStream(double)(没有 Byte/Short 流,用 IntStream 覆盖)。理解「IntStream 直接用 int(不是 Integer 对象、全程无装箱拆箱、不创建对象、运算快省内存)、比 Stream
三、专属数值方法:sum、average
数值流有专门的数值方法,普通 Stream 没有:
数值流专属方法(普通 Stream<Integer> 没有):
sum():求和 → 直接返回 int/long/double
IntStream.of(1,2,3).sum() → 6
average():平均 → OptionalDouble(可能是空流)
max()/min():最值 → OptionalInt
count():元素个数
对比普通 Stream 求和(繁琐):
Stream<Integer> 求和:
stream.reduce(0, Integer::sum) // 要用 reduce,且装箱
或 stream.collect(summingInt(...)) // 收集器
→ 没有直接的 sum()
IntStream 求和:
intStream.sum() // 直接,无装箱
→ 简洁 + 高效
所以:需要对数字求和/平均/最值,用数值流的专属方法
比普通 Stream 的 reduce/collect 简洁且高效
数值流有专属数值方法(普通 Stream<Integer> 没有):sum()(求和,直接返回 int)、average()(平均,返回 OptionalDouble)、max()/min()(最值,OptionalInt)、count()。对比普通 Stream 求和要用 reduce(0, Integer::sum) 或 collect(summingInt)(繁琐且装箱),IntStream 直接 sum()(简洁 + 无装箱)。理解「数值流专属方法 sum()/average()/max()/min()(普通 Stream 没有)、比 reduce/collect 简洁高效、直接对数字求和平均用它们」,就掌握了数值流的专属方法。
四、range:生成数字范围
range/rangeClosed 生成数字范围,替代传统 for 循环:
IntStream.range(a, b):生成 [a, b) 的整数流(不含 b)
IntStream.range(0, 5) → 0, 1, 2, 3, 4
IntStream.rangeClosed(a, b):生成 [a, b] 的整数流(含 b)
IntStream.rangeClosed(1, 5) → 1, 2, 3, 4, 5
替代传统 for 循环:
传统:for (int i = 0; i < 5; i++) { ... }
数值流:IntStream.range(0, 5).forEach(i -> { ... })
配合其他操作:
IntStream.rangeClosed(1, 100).sum() // 1+2+...+100 = 5050
IntStream.rangeClosed(1, 10).filter(i -> i % 2 == 0).sum() // 偶数和
IntStream.range(0, arr.length)
.mapToObj(i -> arr[i] + ":" + i) // 带索引处理数组
其他生成方式:
IntStream.of(1, 2, 3) // 指定元素
IntStream.iterate(1, n -> n*2).limit(10) // 迭代生成
IntStream.generate(...) // 生成器
Arrays.stream(intArray) // 从 int[] 数组
所以 range 让"数字范围的遍历/处理"函数式化
range/rangeClosed 生成数字范围:range(a, b) 生成 [a, b)(不含 b)、rangeClosed(a, b) 生成 [a, b](含 b)。替代传统 for 循环(for(i=0;i<5;i++) → IntStream.range(0,5).forEach(...))。配合操作:rangeClosed(1,100).sum()(1~100 求和 5050)、带索引处理数组。其他生成:IntStream.of、iterate、Arrays.stream(intArray)。理解「range(a,b)生成[a,b)、rangeClosed(a,b)生成[a,b]、替代 for 循环、配合 sum/filter、Arrays.stream(intArray)从数组」,就掌握了 range 生成。
五、summaryStatistics:一次拿所有统计
summaryStatistics() 一次性拿到所有统计值:
summaryStatistics():返回 IntSummaryStatistics(一个统计对象)
一次遍历,拿到 count、sum、min、max、average 全部:
IntSummaryStatistics stats = stream.mapToInt(...).summaryStatistics();
stats.getCount() 元素个数
stats.getSum() 总和
stats.getMin() 最小
stats.getMax() 最大
stats.getAverage() 平均
为什么用它(一次遍历,省事):
如果分别求 count、sum、min、max、average:
要遍历 5 次(每个统计一次)→ 浪费
用 summaryStatistics:一次遍历全算出来 → 高效
配合 groupingBy(分组统计):
groupingBy(Order::getType,
Collectors.summarizingInt(Order::getAmount))
→ Map<类型, IntSummaryStatistics>
→ 每种类型的 count/sum/min/max/average 一次算出
所以需要多个统计值时,summaryStatistics 一次搞定,别分别求
summaryStatistics() 一次遍历拿到所有统计值——返回 IntSummaryStatistics,一个对象里有 getCount/getSum/getMin/getMax/getAverage。为什么用它:分别求 count/sum/min/max/average 要遍历 5 次(浪费),summaryStatistics 一次遍历全算出(高效)。配合 groupingBy + summarizingInt 做分组统计(每组的所有统计值)。理解「summaryStatistics 一次遍历拿所有统计值(count/sum/min/max/average)、比分别求(遍历5次)高效、配合 groupingBy+summarizingInt 分组统计」,就掌握了 summaryStatistics。
六、互转与实践
数值流和对象流的互转,以及实践建议:
对象流 → 数值流:
mapToInt/mapToLong/mapToDouble(取值函数)
list.stream().mapToInt(Order::getAmount) // Stream<Order> → IntStream
→ 提取数字字段、转成数值流做数值运算
数值流 → 对象流:
boxed():int → Integer(装箱回对象流)
IntStream.range(0,5).boxed() → Stream<Integer>
→ 需要收集成 List<Integer> 时用(toList 需要对象流)
mapToObj(fn):int → 任意对象
IntStream.range(0,5).mapToObj(i -> "item" + i) → Stream<String>
实践建议:
① 对数字求和/平均/统计 → mapToInt 转数值流,用 sum/average/summaryStatistics
list.stream().mapToInt(Order::getAmount).sum()
(而非 map(...).reduce(0, Integer::sum) 一路装箱)
② 数字范围遍历 → IntStream.range/rangeClosed(替代 for)
③ 需要收集成 List<Integer> → 数值流 boxed() 再 collect
④ 多个统计值 → summaryStatistics 一次算
一句话:处理数字用数值流(避免装箱、有专门方法),
对象流→数值流用 mapToInt,数值流→对象流用 boxed/mapToObj
数值流和对象流互转:对象流→数值流用 mapToInt(提取数字字段做数值运算);数值流→对象流用 boxed(int→Integer,需要 collect 成 List<Integer> 时)或 mapToObj(int→任意对象)。实践:求和/平均/统计用 mapToInt+sum/average/summaryStatistics(而非 map+reduce 一路装箱)、数字范围用 range/rangeClosed、收集成 List
记忆钩子:「IntStream/LongStream/DoubleStream=基本类型特化流,核心价值避免 Stream
的装箱拆箱开销(Integer 是堆上对象、运算要拆箱、大量数据时开销+内存+GC 压力),数值流直接用 int(无装箱、省内存);专属方法:sum()/average()/max()/min()(普通 Stream 没有、比 reduce 简洁);range(a,b)生成[a,b)/rangeClosed[a,b]替代 for 循环;summaryStatistics()一次遍历拿 count/sum/min/max/average(比分别求高效);互转:对象流→数值流 mapToInt、数值流→对象流 boxed(装箱)/mapToObj;处理数字用 mapToInt.sum 别用 map.reduce(一路装箱)」 。
七、常见误区与追问
- 误区:Stream
和 IntStream 性能一样。 不一样——Stream的元素是 Integer 对象,数值运算要装箱/拆箱、创建对象、有 GC 压力;IntStream 直接用 int(基本类型),全程无装箱拆箱、不创建对象、更快更省内存;大量数字处理差别明显。 - 误区:求一堆数字的和用 stream().map(…).reduce(0, Integer::sum)。 这样一路装箱、低效;应该用 list.stream().mapToInt(…).sum()——mapToInt 转成 IntStream(直接用 int),sum() 直接求和、无装箱;简洁且高效。
- 误区:range(0, 5) 包含 5。 range(a, b) 是左闭右开 [a, b)、不含 b——range(0, 5) 是 0,1,2,3,4;要包含 b 用 rangeClosed(a, b)(闭区间 [a, b])——rangeClosed(1, 5) 是 1,2,3,4,5。
- 误区:数值流能直接 collect(Collectors.toList())。 不能直接——Collectors.toList() 需要对象流;数值流要先 boxed() 装箱成 Stream
再 collect(toList()),或用数值流自己的 toArray() 拿 int[];数值流的 collect 签名和对象流不同。 - 追问:为什么处理数字要用 IntStream 而不是 Stream
? 避免装箱拆箱开销——Stream的每个元素是 Integer 对象(堆上),做数值运算要拆箱成 int 计算、有时装箱回 Integer,大量数据时这些装箱/拆箱 + 对象创建 + GC 开销累积很可观;IntStream 直接用 int(栈上基本类型),全程无装箱、不创建对象,更快更省内存。 - 追问:summaryStatistics 有什么用? 一次遍历拿到所有统计值——返回 IntSummaryStatistics,一个对象里就有 getCount/getSum/getMin/getMax/getAverage;如果分别求这些统计值要遍历流 5 次,summaryStatistics 一次遍历全算出来、高效;配合 groupingBy + summarizingInt 还能做分组统计(每组的所有统计值)。
- 追问:对象流和数值流怎么互转? 对象流→数值流:mapToInt/mapToLong/mapToDouble(传取值函数,如 list.stream().mapToInt(Order::getAmount));数值流→对象流:boxed()(装箱回 Stream
,需要 collect 成 List 时用)或 mapToObj(fn)(映射成任意对象,如 IntStream.range(0,5).mapToObj(i -> “item”+i))。
八、加强记忆
IntStream、LongStream、DoubleStream 是「基本类型特化流」——处理 int/long/double 时直接用基本类型,避免普通 Stream<Integer> 的装箱/拆箱开销。核心价值是避免装箱拆箱:Stream<Integer> 的元素是 Integer 对象(堆上),数值运算要拆箱成 int 算、有时装箱回 Integer,大量数据时装箱拆箱 + 对象创建 + GC 开销可观;数值流直接用 int(栈上基本类型),全程无装箱、不创建对象、更快更省内存。专属数值方法(普通 Stream 没有):sum()、average()(OptionalDouble)、max()/min()(OptionalInt)——比 reduce(0, Integer::sum) 简洁高效。range(a,b) 生成 [a,b)、rangeClosed(a,b) 生成 [a,b]——替代传统 for 循环。summaryStatistics() 一次遍历拿 count/sum/min/max/average(IntSummaryStatistics,比分别求遍历 5 次高效,配 groupingBy+summarizingInt 做分组统计)。互转:对象流→数值流用 mapToInt(提取数字字段),数值流→对象流用 boxed(装箱,需 collect 成 ListmapToObj(映射成对象)。实践:处理数字用 mapToInt(...).sum()(别用 map(...).reduce(...) 一路装箱)。一句话「数值流 IntStream/LongStream/DoubleStream 直接用基本类型避免 Stream