流处理必须确保源头和元素均非空:源集合需用Collections.emptyList()兜底或safeStream封装;流中用filter(Objects::nonNull)前置过滤null元素;对可选数据宜用Stream<Optional<T>>配合flatMap展开;关键聚合可自定义NonEmptyCollector拦截null。

流(Stream)本身不接受 null 元素,一旦源集合含 null 或流中间操作产生 null,调用终端操作(如 map、filter、collect)时极易触发 NullPointerException。要真正拦截异常路径,不能靠“流里加 if”,而应把非空校验前置到流创建之前、或封装进流构建环节,让 null 根本无法进入流处理管道。
流源头必须非空:集合/数组先兜底再转流
流的起点(如 list.stream())若传入 null,会立即抛出 NPE。因此所有流入口都需强制保障源对象非空:
- DAO 层查询返回值统一用
Collections.emptyList()替代null,避免userDao.findAll() == null导致后续.stream()崩溃 - DTO 或外部输入的集合字段,应在构造器或
@PostConstruct中初始化:private final List<Item> items = new ArrayList<>(); - 手动创建流时,用工具方法封装:
Stream<T> safeStream(Collection<T> c) { return Optional.ofNullable(c).orElse(Collections.emptyList()).stream(); }
流元素必须非空:在 map/filter 前过滤掉 null 元素
即使集合非空,其中元素仍可能为 null(如数据库字段允许 NULL、JSON 反序列化失败)。直接 stream().map(User::getName) 会崩在第一个 null 上。正确做法是:
- 用
filter(Objects::nonNull)作为流的第一步操作,显式剔除 null 元素:list.stream().filter(Objects::nonNull).map(User::getName).collect(...) - 对已知可能含 null 的字段(如
user.getProfile()),改用map(u -> Optional.ofNullable(u.getProfile()).map(p -> p.getAvatar()).orElse(null)),保持链路安全 - 避免在
map内部做判空——那只是掩盖问题;filter是声明式守门,语义清晰且可复用
用 Optional 流替代原始流,显式表达可选性
当数据源天然存在“缺失”语义(如查用户头像、获取配置项),不应强行塞进普通 Stream,而应设计为 Stream<Optional<T>>:
- 例如:从多个服务并行获取用户信息,某服务超时返回
Optional.empty(),可组装成Stream.of(opt1, opt2, opt3) - 后续用
flatMap(Optional::stream)展开有效值,自动跳过 empty,无需额外filter - 这样既保留了“可能存在”的契约,又让空值处理集中在流构建阶段,业务逻辑层只面对确定非空的元素
自定义 Collector 封装空安全聚合逻辑
终端操作如 Collectors.toList() 不校验元素,但某些聚合场景(如统计、分组)要求元素非空。可通过自定义 Collector 实现前置拦截:
- 定义
NonEmptyCollector,在accumulator中先Objects.requireNonNull(item, "item must not be null") - 或使用
Collectors.collectingAndThen(Collectors.toList(), list -> { if (list.contains(null)) throw new IllegalArgumentException("null item detected"); return list; }) - 将该 Collector 注册为 Spring Bean,在关键业务流中统一注入,避免重复校验

















