filter() 必须放在 map() 前面是因逻辑合理性:先校验数据有效性(如含逗号且分割后长度为2),再执行拆分转换,避免空指针或数组越界异常。

filter() 为什么必须放在 map() 前面?
不是语法强制,而是逻辑合理性问题。比如你有一组字符串 "zhangsan,23",想筛出年龄 ≥24 的人再取姓名——如果先 map() 拆分再 filter(),会因空指针或数组越界直接抛 ArrayIndexOutOfBoundsException;而先 filter() 再 map(),能确保每条数据都满足结构前提。
常见错误现象:NullPointerException 或 NumberFormatException,往往是因为没校验字符串格式就急着 split(",")[1]。
- 始终在
filter()中完成「数据有效性判断」:如s -> s.contains(",") && s.split(",").length == 2 - 避免在
map()里做条件分支逻辑,它只负责「一对一转换」 - 若需复杂转换+过滤混合,优先拆成两个独立
stream()调用,别硬塞进一个链式调用
collect(Collectors.toList()) 不是唯一出口
终端操作选错,轻则性能浪费,重则类型不匹配。比如你最终要的是一个 Map<string integer></string>,却写 collect(Collectors.toList()),后面还得遍历转 Map,白耗一次迭代。
使用场景决定收集器:
- 需要键值对映射 → 用
Collectors.toMap(keyMapper, valueMapper),注意 key 重复会抛IllegalStateException - 只要统计个数 → 直接
count(),比collect(...).size()少建中间集合 - 只取第一个匹配项 →
findFirst(),短路执行,比limit(1).collect(...)更高效 - 要避免 null 值进入 Map → 在
toMap()后加第三个参数(existing, replacement) -> existing处理冲突
Stream 只能消费一次,别反复用同一个 stream 变量
这是最常被忽略的隐性坑。比如你写了 Stream<string> s = list.stream().filter(...); List r1 = s.collect(...); List r2 = s.collect(...);</string>,第二行会报 java.lang.IllegalStateException: stream has already been operated upon or closed。
根本原因:Stream 是一次性流水线,终端操作触发后即关闭。
- 每次需要新结果,都重新调用
list.stream(),不要缓存 Stream 对象 - 如果原始数据开销大(如从 DB 查),先
collect(Collectors.toList())落到内存,再对副本多次流式处理 - 调试时别在 IDE 的「Evaluate Expression」里反复执行同一段 stream 链式调用,容易误判为代码问题
基础方法组合的实际写法示例
假设你有 List<string> raw = Arrays.asList("alice,30", "bob,25", "carol,35", "dan,invalid");</string>,目标:提取年龄 ≥30 的合法记录,构造 Map<string integer></string>。
Map<string integer> result = raw.stream()
.filter(s -> s.contains(",") && s.split(",").length == 2) // 先保结构
.filter(s -> {
try {
return Integer.parseInt(s.split(",")[1]) >= 30;
} catch (NumberFormatException e) {
return false;
}
})
.collect(Collectors.toMap(
s -> s.split(",")[0],
s -> Integer.parseInt(s.split(",")[1])
));</string>
这里两个 filter() 分工明确:第一个守门(格式),第二个守规(数值)。强行合并成一个 lambda 会让可读性和可维护性断崖下跌。
真正难的不是记住语法,而是判断哪一步该提前拦截、哪一步该延迟计算、以及什么时候该放弃链式而选择分步 —— 这些边界感,得在真实脏数据里撞几次才立得住。











