1. 概述

本文将介绍如何从 Java 集合中创建空安全(null-safe)的 Stream,避免因 null 引用导致运行时 NullPointerException

要理解本文内容,需具备 Java 8 的以下知识基础:

  • 方法引用(Method References)
  • Lambda 表达式
  • Optional
  • Stream API

如果对上述内容不熟悉,建议先阅读以下文章:

2. Maven 依赖

某些方案需要引入 Apache Commons Collections 库:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.5.0-M2</version>
</dependency>

该依赖可在 Maven Central 获取。

⚠️ 注意:如果你的项目不允许引入第三方库,方案 4.2 就得 pass 掉。

3. 从集合创建 Stream 的基本方式

最直接的方式是调用集合的 .stream().parallelStream() 方法:

Collection<String> collection = Arrays.asList("a", "b", "c");
Stream<String> streamOfCollection = collection.stream();

实际开发中,我们常封装成方法,比如:

public Stream<String> collectionAsStream(Collection<String> collection) {
    return collection.stream();
}

踩坑警告:如果传入的 collectionnull,上面这行代码会直接抛出 NullPointerException

所以,问题来了:如何让这个 Stream 创建过程对 null 更 robust?

4. 让集合生成的 Stream 支持 null 安全

4.1. 手动判空(简单粗暴但不优雅)

最直观的方式是在调用 .stream() 前加个 null 判断:

public Stream<String> collectionAsStream(Collection<String> collection) {
    return collection == null 
      ? Stream.empty() 
      : collection.stream();
}

优点:逻辑清晰,不依赖外部库。
缺点

  • 判空代码干扰业务逻辑,降低可读性
  • null 表示“无值”在 Java 8+ 已被认为是过时做法
  • 空集合(empty)和 null 集合语义不同:前者是“查到了但没数据”,后者可能是“压根没查到结果或出错了”

📌 小贴士:空集合 ≠ null 集合,语义差异要搞清楚。

4.2. 使用 Apache Commons 的 CollectionUtils.emptyIfNull

Apache Commons Collections 提供了 CollectionUtils.emptyIfNull() 方法,能将 null 转为不可变的空集合:

import org.apache.commons.collections4.CollectionUtils;

public Stream<String> collectionAsStream(Collection<String> collection) {
    return CollectionUtils.emptyIfNull(collection).stream();
}

优点:代码简洁,一行搞定。
缺点:强依赖 commons-collections4,有些公司技术栈不允许引入。

📌 适用场景:项目已引入 Commons,或者团队允许使用。

4.3. 使用 Java 8 的 Optional(推荐方案)

这才是 Java 8+ 的正确打开方式。用 Optional 来优雅处理可能为 null 的集合:

public Stream<String> collectionToStream(Collection<String> collection) {
    return Optional.ofNullable(collection)
      .map(Collection::stream)
      .orElseGet(Stream::empty);
}

我们来拆解一下这行代码的执行流程:

  1. Optional.ofNullable(collection)

    • 如果 collection != null → 返回 Optional<Collection>
    • 如果 collection == null → 返回空的 Optional
  2. .map(Collection::stream)

    • 只有 Optional 有值时才会执行 collection.stream()
    • 如果 Optional 为空,map 不执行,直接跳过
  3. .orElseGet(Stream::empty)

    • 如果前面 Optional 为空(即原集合为 null),则返回一个空 Stream

优点

  • 无外部依赖
  • 语义清晰,符合函数式编程风格
  • 充分利用了 Java 8 的 Optional 特性

📌 结论:这是最推荐的方案,兼顾安全性、可读性和现代 Java 风格。

4.4. 使用 Java 9 的 Stream.ofNullable(进阶用法)

注意,Stream.ofNullable 并不是用来处理“集合为 null”的,而是处理“集合中元素为 null”的场景。

比如,你想过滤掉集合中的 null 元素,可以这样写:

Stream<String> collectionAsStream(Collection<String> collection) {  
    return collection.stream()
                     .flatMap(s -> Stream.ofNullable(s));
}

📌 解释:

  • Stream.ofNullable(s):如果 s != null,返回单元素 Stream;如果 s == null,返回空 Stream
  • flatMap 会把每个元素映射成一个 Stream,然后“拍平”成一个整体 Stream
  • 最终效果:自动过滤掉 null 元素

⚠️ 注意:这个方法不能解决集合本身为 null 的问题!如果 collectionnull.stream() 依然会 NPE。

所以它和前面几个方案不冲突,更多是互补关系。

5. 总结

方案 是否推荐 说明
✅ 手动判空 ⚠️ 一般 简单但破坏代码美感
✅ Commons emptyIfNull ✅ 可选 依赖第三方库
Optional.ofNullable + map + orElseGet ✅✅✅ 强烈推荐 无依赖,语义清晰,Java 8 正道
Stream.ofNullable ✅ 场景专用 用于处理元素为 null,非集合为 null

📌 最终建议

  • 如果你在写公共库或追求代码质量,优先使用 Optional 方案
  • 如果项目已引入 Commons,emptyIfNull 也完全可用
  • Stream.ofNullable 适合处理“元素可能为空”的流式过滤场景

💡 源码已上传 GitHub:https://github.com/baeldung/tutorials/tree/master/core-java-modules/core-java-collections-2


原始标题:Java Null-Safe Streams from Collections | Baeldung