filter()和map()是Stream API最常用的中间操作,需注意空指针、类型匹配与副作用;filter用Predicate返回boolean,map用Function可返回null;链式调用时应先判空再操作,避免NPE和类型错误。

filter() 和 map() 是 Stream API 最常用的两个中间操作
直接用 filter() 做条件筛选,用 map() 做数据转换,两者可链式调用。关键不是“能不能做”,而是“怎么写才不踩坑”——比如空指针、类型不匹配、副作用误用。
-
filter()接收Predicate<t></t>,必须返回boolean;传入null会抛NullPointerException -
map()接收Function<t r></t>,返回值可以是null,但后续若再调用collect()(如Collectors.toList())不会报错,只是把null当普通元素存进去 - 别在
filter()或map()里修改原始集合,Stream 是不可变的,原集合不受影响
常见错误:filter 中判 null 不彻底导致 NPE
比如对 List<string></string> 过滤非空且长度大于 3 的字符串,写成 filter(s -> s.length() > 3) 就会崩——因为没先判 s != null。
list.stream()
.filter(s -> s != null && s.length() > 3)
.collect(Collectors.toList());
- 更安全的做法是用
Objects.nonNull():filter(Objects::nonNull) - 如果业务允许,优先在源头清理 null(如构造集合时过滤掉 null),比每次流式处理都判空更高效
- 注意
Optional不适用于filter()的谓词逻辑——它不能直接当Predicate用
map() 后类型变了,collect 时要注意泛型推导
比如从 List<integer></integer> 映射成 List<string></string>,map(String::valueOf) 没问题,但若映射成 Map 或自定义对象,容易在 collect() 阶段因类型不匹配编译失败。
list.stream()
.map(i -> new Person("user" + i, i))
.collect(Collectors.toList()); // 返回 List<Person>
- 别依赖 IDE 自动补全的泛型,显式写清楚
collect(Collectors.toList())的目标类型(必要时加类型标注,如.<person>collect(...)</person>) - 避免在
map()里做重 IO 或耗时计算——Stream 默认不保证并行安全,且延迟求值特性会让性能问题更隐蔽 - 如果映射结果需要去重或排序,优先考虑
map()后接distinct()或sorted(),而不是提前转成Set再转回 stream
实际组合场景:一次流完成过滤+转换+聚合
例如统计满足条件的字符串长度之和:
int sum = list.stream()
.filter(Objects::nonNull)
.filter(s -> s.startsWith("a"))
.mapToInt(String::length)
.sum();
-
mapToInt()比map()+collect(summingInt())更轻量,避免装箱开销 - 多个
filter()可以合并为一个,但拆开更利于单元测试和复用(比如抽成Predicate常量) - 如果最终要的是单个对象(如第一个匹配项),用
findAny()或findFirst(),别用collect()+get(0)——前者短路,后者强制遍历全部
真正麻烦的不是语法,是搞清每一步的返回类型、是否短路、是否可并行,以及原始集合有没有被意外修改。写完记得用小数据集跑一遍,尤其测 null 和边界值。

















