273. Java Stream API - Stream 中的中间操作:Mapping 操作详解
🧩 什么是 Mapping?
在 Stream 中,**Mapping(映射)*是指使用某个函数将流中的每个元素*转换成另一个元素,可以是不同类型,也可以是同类型。例如:
- 把字符串转换为其长度(类型变化:
String → Integer) - 把每个整数平方(类型不变:
Integer → Integer)
🧪 示例 1:基础 map() 操作(注意没有终端操作!)
java
List<String> strings = List.of("one", "two", "three", "four");
Function<String, Integer> toLength = String::length;
Stream<Integer> ints = strings.stream()
.map(toLength);
System.out.println("Done processing");
👀 输出是什么?
java
Done processing
✅ 编译通过,也运行了,但......没有任何数据处理。
📌 原因:map() 是 中间操作 ,具有惰性求值 特性。只有在调用终端操作(如 toList()、forEach())时,流才开始执行。
✅ 示例 2:加入终端操作 toList()
java
List<String> strings = List.of("one", "two", "three", "four");
List<Integer> lengths = strings.stream()
.map(String::length)
.toList(); // 🚀 触发执行
System.out.println("lengths = " + lengths);
输出:
java
lengths = [3, 3, 5, 4]
🎯 这个版本中:
- 使用
map()把每个字符串转换为它的长度(类型变化:String → Integer) - 使用
toList()把处理结果收集到列表中,从而触发整个流水线的执行
🚀 使用 mapToInt():转为 IntStream,性能更高!
如果我们不需要装箱后的 Integer 对象,而是直接处理原始 int 类型,可以使用 mapToInt() 方法。
java
List<String> strings = List.of("one", "two", "three", "four");
IntStream lengths = strings.stream()
.mapToInt(String::length); // 返回 IntStream
⚠️ 和 map() 不同,这里返回的是 IntStream,一个专用于 int 的流,避免了装箱,提高性能。
📊 示例 3:统计字符串长度的各种统计数据
IntStream 提供了非常实用的终端操作 ------ summaryStatistics(),一次性获得所有统计信息。
java
List<String> strings = List.of("one", "two", "three", "four");
IntSummaryStatistics stats = strings.stream()
.mapToInt(String::length)
.summaryStatistics();
System.out.println("stats = " + stats);
输出:
java
stats = IntSummaryStatistics{count=4, sum=15, min=3, average=3.750000, max=5}
✅ 一次性获得:
count:元素数量sum:总长度min:最短长度max:最长长度average:平均长度
🧠 补充知识:map() vs. mapToInt()
| 方法 | 输入类型 | 输出类型 | 是否装箱 | 用途 |
|---|---|---|---|---|
map() |
Function<T, R> |
Stream<R> |
是 | 处理任意对象类型 |
mapToInt() |
ToIntFunction<T> |
IntStream |
否 | 专门用于处理整数类型,避免装箱 |
mapToLong() |
ToLongFunction<T> |
LongStream |
否 | 用于处理 long 类型 |
mapToDouble() |
ToDoubleFunction<T> |
DoubleStream |
否 | 用于处理 double 类型 |
✔ 这些 mapToX 方法适合在你需要高性能数值计算时使用,比如统计、汇总等操作。
🔁 惰性求值:中间操作不会立刻执行!
你写的 map() 并不会马上对元素做转换,Stream 就像一个懒惰的员工,直到你给它终点(终端操作),它才真正开始干活。
| 中间操作示例 | 是否触发执行? | 备注 |
|---|---|---|
.map(...) |
❌ | 构建 pipeline,不执行 |
.filter(...) |
❌ | 构建 pipeline,不执行 |
.sorted() |
❌ | 构建 pipeline,不执行 |
.toList() |
✅ | 终端操作,触发执行 |
.forEach() |
✅ | 终端操作,触发执行 |
.count() |
✅ | 终端操作,触发执行 |
🧪 对比示例:map 和 mapToInt 效果一致,但行为不同
java
List<String> strings = List.of("a", "bb", "ccc");
List<Integer> boxed = strings.stream()
.map(String::length)
.toList(); // 返回 List<Integer>
IntSummaryStatistics stats = strings.stream()
.mapToInt(String::length)
.summaryStatistics(); // 原始类型统计
🚀 boxed 版本使用的是 Integer 包装类型,适合进一步映射或收集; 📈 stats 版本用的是 IntStream,适合数值统计和计算。
🧭 小结
| 关键点 | 说明 |
|---|---|
| 中间操作(如 map)是惰性的 | 不会立即执行,必须通过终端操作触发 |
| map 可改变类型 | 可将 String 映射为 Integer、Double、Long 等 |
使用 mapToX 避免装箱开销 |
高性能数值流:IntStream、LongStream、DoubleStream |
summaryStatistics() 很强大 |
一次性获取 count、sum、min、max、average |
| 不带终端操作的流什么也不会做 | 要训练学员看到 .map() 就联想到:是不是漏写了 .toList()? |