1. 概述
熟练使用 Java 的集合框架是每个开发者的核心技能。本文聚焦 ArrayList
及其 addAll()
方法,探讨如何优雅处理其中的 null
值问题。虽然 addAll()
是批量添加元素的便捷方式,但遇到 null
时容易踩坑。
2. Null 值与 addAll() 的冲突
直接将 null
传递给 addAll()
会触发 NullPointerException
,这是开发者常遇到的陷阱:
@ParameterizedTest
@NullSource
void givenNull_whenAddAll_thenAddThrowsNPE(List<String> list) {
ArrayList<String> strings = new ArrayList<>();
assertThatExceptionOfType(NullPointerException.class)
.isThrownBy(() -> strings.addAll(list));
}
⚠️ 虽然异常明确,但问题只能在运行时暴露,缺乏编译时保护。
3. 基础防御方案
最直接的解决方案是添加显式的 null
检查:
@ParameterizedTest
@NullSource
void givenNull_whenAddAllWithCheck_thenNoNPE(List<String> list) {
ArrayList<String> strings = new ArrayList<>();
assertThatNoException().isThrownBy( () -> {
if (list != null) {
strings.addAll(list);
}
});
}
✅ 这种方式简单可靠,但代码略显啰嗦,偏向命令式编程风格。
4. 封装检查逻辑
将 null
检查抽取为独立方法,提升代码可读性:
private static void addIfNonNull(List<String> list, ArrayList<String> strings) {
if (list != null) {
strings.addAll(list);
}
}
调用端代码更简洁:
@ParameterizedTest
@NullSource
void givenNull_whenAddAllWithExternalizedCheck_thenNoNPE(List<String> list) {
ArrayList<String> strings = new ArrayList<>();
assertThatNoException().isThrownBy( () -> {
addIfNonNull(list, strings);
});
}
⚠️ 权衡点:虽然可读性提升,但方法内部直接修改外部集合,可能引发副作用。
5. 默认空集合策略
使用 Apache Commons 的 CollectionUtils
将 null
转换为空集合:
@ParameterizedTest
@NullSource
void givenNull_whenAddAllWithCollectionCheck_thenNoNPE(List<String> list) {
ArrayList<String> strings = new ArrayList<>();
assertThatNoException().isThrownBy( () -> {
strings.addAll(CollectionUtils.emptyIfNull(list));
});
}
✅ 最佳实践:在数据入口处尽早转换 null
,避免污染后续流程(类似 Kotlin 的空安全设计)。
6. Optional 优雅方案
利用 Java 8 的 Optional
实现链式调用:
@ParameterizedTest
@NullSource
void givenNull_whenAddAllWithOptional_thenNoNPE(List<String> list) {
ArrayList<String> strings = new ArrayList<>();
assertThatNoException().isThrownBy( () -> {
Optional.ofNullable(list).ifPresent(strings::addAll);
});
}
✅ 无需第三方库,代码简洁且意图明确,符合函数式编程风格。
7. Stream 过滤方案
处理批量 List
时,用 Stream 的 filter()
过滤 null
:
@ParameterizedTest
@MethodSource("listProvider")
void givenCollectionOfNullableLists_whenFilter_thenNoNPE(List<List<String>> listOfLists) {
ArrayList<String> strings = new ArrayList<>();
assertThatNoException().isThrownBy(() -> {
listOfLists.stream().filter(Objects::nonNull).forEach(strings::addAll);
});
}
✅ 特别适合处理集合中的可空元素,与 Optional
风格一致。
8. 总结
处理 null
值是保障健壮性的关键环节。本文提供了多种解决方案:
方案 | 优点 | 缺点 |
---|---|---|
基础检查 | 简单直接 | 代码冗余 |
封装方法 | 提升可读性 | 存在副作用 |
空集合转换 | 防御性强 | 依赖外部库 |
Optional | 链式优雅 | 需 Java 8+ |
Stream | 批量处理友好 | 仅适用集合场景 |
💡 核心建议:优先在数据源头控制 null
传播,避免后续防御式编程。最佳实践是从根本上消除 null
值(如使用 Kotlin 空安全或设计非空契约)。
完整代码示例请查阅 GitHub 仓库。