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();
}
❌ 踩坑警告:如果传入的 collection
是 null
,上面这行代码会直接抛出 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);
}
我们来拆解一下这行代码的执行流程:
✅
Optional.ofNullable(collection)
- 如果
collection != null
→ 返回Optional<Collection>
- 如果
collection == null
→ 返回空的Optional
- 如果
✅
.map(Collection::stream)
- 只有
Optional
有值时才会执行collection.stream()
- 如果
Optional
为空,map
不执行,直接跳过
- 只有
✅
.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
,返回空 StreamflatMap
会把每个元素映射成一个 Stream,然后“拍平”成一个整体 Stream- 最终效果:自动过滤掉
null
元素
⚠️ 注意:这个方法不能解决集合本身为 null 的问题!如果 collection
是 null
,.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