276. Java Stream API - 使用 flatMap 和 mapMulti 清理数据并转换类型
🎯 场景需求:清理并转换用户输入的数据
你收到一个 List<String>,每个字符串理论上代表一个整数,但实际上这些字符串可能:
- 包含空格(如
"3 ") - 是空字符串
"" - 甚至是
null(这里没有写出,但应当处理) - 包含非法字符(如
"abc")
你的目标是:只保留可以成功转换为整数的字符串 ,并将它们转为 List<Integer>。
❌ 错误示例:filter + map 是不合适的
很多人第一反应可能是这样:
java
Predicate<String> isANumber = s -> {
try {
Integer.parseInt(s);
return true;
} catch (NumberFormatException e) {
return false;
}
};
List<Integer> ints = strings.stream()
.filter(isANumber) // 检查
.map(Integer::parseInt) // 再转
.toList();
但这有两个大问题:
- ❌ 重复解析:
Integer.parseInt(s)被调用了两次,浪费性能。
- ❌ try-catch 返回布尔值是设计异味:
- 把异常逻辑用于逻辑判断,可读性差。
✅ 正确示例:用 flatMap 一次性搞定!
你可以用 flatMap 结合 try-catch 实现清洗 + 转换,只要转换成功就返回一个单元素流,失败就返回空流。
🔨 示例代码:
java
Function<String, Stream<Integer>> flatParser = s -> {
try {
return Stream.of(Integer.parseInt(s));
} catch (NumberFormatException e) {
return Stream.empty(); // 解析失败,直接跳过
}
};
List<String> strings = List.of("1", " ", "2", "3 ", "", "3");
List<Integer> ints = strings.stream()
.flatMap(flatParser)
.toList();
System.out.println("ints = " + ints);
✅ 输出:
java
ints = [1, 2, 3]
🌟 所有无效数据(如 " "、""、"3 ")都被优雅地丢弃了。
🆕 Java 16 新方法:mapMulti 更高效!
虽然 flatMap 很强大,但它会为每个元素都生成一个新的 Stream 对象,性能不如一次性收集高效。
💡 mapMulti() 是更高效的替代:
它通过回调方式,决定是否将元素加入最终结果流,避免了不必要的中间流。
🧩 示例代码:
java
List<String> strings = List.of("1", " ", "2", "3 ", "", "3");
List<Integer> ints = strings.stream()
.<Integer>mapMulti((string, consumer) -> {
try {
consumer.accept(Integer.parseInt(string));
} catch (NumberFormatException ignored) {
// 什么也不做,自动丢弃无效数据
}
})
.toList();
System.out.println("ints = " + ints);
✅ 输出:
java
ints = [1, 2, 3]
🧠 mapMulti() 是如何工作的?
| 动作 | 意义 |
|---|---|
string |
当前流中要处理的元素 |
consumer.accept(x) |
将 x 放入最终的 stream 输出 |
不调用 accept() |
表示此元素无效,不进入最终输出(相当于被过滤) |
👀 相比 flatMap,mapMulti 不创建额外的流,适合高性能场景。
⚠️ 注意:类型推断语法
你需要手动告诉编译器输出元素类型:
java
.<Integer>mapMulti(...)
如果你省略它:
java
.mapMulti(...)
编译器将默认输出 Stream<Object>,这会导致后续类型错误。
🧪 总结对比
| 特性 | flatMap |
mapMulti |
|---|---|---|
| 引入版本 | Java 8 | Java 16 |
| 用法 | 返回 Stream(可能是空) | 使用 consumer.accept() 添加元素 |
| 性能 | 为每个元素构建流(较慢) | 不生成中间流(更高效) |
| 适合场景 | 结构映射 & 扁平化 | 条件过滤 & 数据转换 |
🧩 延伸建议:泛型类型安全版 flatParser
如果你担心 null 或更复杂的转换,可以将函数封装为更通用版本:
java
static <T> Function<String, Stream<T>> safeParse(Function<String, T> parser) {
return s -> {
try {
return Stream.of(parser.apply(s));
} catch (Exception e) {
return Stream.empty();
}
};
}
使用:
java
strings.stream()
.flatMap(safeParse(Integer::parseInt))
.toList();