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 的 CollectionUtilsnull 转换为空集合:

@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 仓库


原始标题:Handling Nulls in ArrayList.addAll() | Baeldung