在 Java 开发中,列表初始化看似简单,但选错方式可能导致线上运行时报错。如果你需要创建一个后续不会修改的常量列表,直接用 Java 9 引入的 List.of() 最省事;如果需要往列表里增删元素,或者数据里可能包含 null,请继续使用 new ArrayList<>(Arrays.asList(...)) 或常规 add 方式,避免运行时报错。
先说结论:List.of() 创建的是不可变列表,传统 ArrayList 初始化创建的是可变列表,两者在能否修改、是否允许 null 以及底层实现上完全不同。
- 适合:数据确定不变且不含 null 的场景用 List.of(),需要动态修改或含 null 用 ArrayList。
- 重点看:List.of() 调用 add/remove/set 会抛 UnsupportedOperationException,含 null 会抛 NullPointerException。
- 别忽略:Arrays.asList() 返回的列表大小固定且受原数组影响,不完全等同于 ArrayList 初始化。
核心机制差异
List.of() 是 Java 9 引入的静态工厂方法,设计初衷就是为了创建不可变集合。它底层会复制一份数组并检查 null,返回的对象任何修改操作(包括 set)都会直接抛异常。而传统的 new ArrayList() 或者 Arrays.asList() 主要是为了兼容旧版本和提供可变性。
特别注意 Arrays.asList(),它虽然看起来像列表,但它只是原数组的视图,大小固定,修改元素会影响原数组,且不允许增删。这是一个常见的隐蔽风险点。
完整可运行代码示例
以下是一个完整的 Main 类示例,涵盖了三种初始化方式及其行为验证。你可以直接复制到 IDE 中运行,观察控制台输出。
import java.util.*;
public class ListInitializationDemo {
public static void main(String[] args) {
// 场景 1: Java 9+ 不可变列表
try {
List<String> immutableList = List.of("a", "b", "c");
System.out.println("List.of 创建成功:" + immutableList);
immutableList.add("d"); // 此处会报错
} catch (Exception e) {
System.out.println("List.of 异常:" + e.getClass().getSimpleName() + " - " + e.getMessage());
}
// 场景 2: Arrays.asList 视图列表
try {
String[] array = {"x", "y", "z"};
List<String> backedList = Arrays.asList(array);
System.out.println("Arrays.asList 创建成功:" + backedList);
backedList.add("w"); // 此处会报错
array[0] = "changed"; // 修改数组会影响列表
System.out.println("修改原数组后列表变为:" + backedList);
} catch (Exception e) {
System.out.println("Arrays.asList 异常:" + e.getClass().getSimpleName() + " - " + e.getMessage());
}
// 场景 3: 标准 ArrayList 可变列表
try {
List<String> mutableList = new ArrayList<>(Arrays.asList("1", "2", "3"));
System.out.println("new ArrayList 创建成功:" + mutableList);
mutableList.add("4"); // 操作成功
mutableList.set(0, null); // 允许 null
System.out.println("修改后列表:" + mutableList);
} catch (Exception e) {
System.out.println("new ArrayList 异常:" + e.getClass().getSimpleName());
}
}
}异常堆栈与排查
在生产环境中,如果误用了 List.of() 并进行修改,日志中会出现明确的异常信息。以下是典型的错误堆栈,有助于快速定位问题。
1. 尝试修改不可变列表:
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:71)
at java.base/java.util.ImmutableCollections$AbstractImmutableList.add(ImmutableCollections.java:141)
at com.example.ListInitializationDemo.main(ListInitializationDemo.java:10)2. 尝试存入 null 值:
Exception in thread "main" java.lang.NullPointerException
at java.base/java.util.Objects.requireNonNull(Objects.java:222)
at java.base/java.util.List.of(List.java:995)
at com.example.ListInitializationDemo.main(ListInitializationDemo.java:5)看到 java.util.ImmutableCollections 或 List.of 相关的栈帧,即可确认是使用了不可变集合导致的错误。
迁移与避坑指南
在老项目升级 Java 9+ 或重构代码时,请遵循以下步骤:
- 确认需求:列表创建后是否需要 add、remove 或 set 操作?是否需要存 null 值?
- 选择 API:如果不需要修改且无 null,优先 List.of() 代码更简洁;否则用 new ArrayList<>() 包装。
- 注意版本:List.of() 需要 Java 9 及以上环境,老项目升级需谨慎。
- 警惕直接替换:将旧代码中的 new ArrayList<>(Arrays.asList(...)) 直接改为 List.of(),若后续有修改操作或含 null,上线必崩。
- 数据源清洗:List.of() 对参数逐个判空,数据源不干净时容易抛 NullPointerException,建议在调用前过滤 null。
参考来源
- Oracle Java SE 9 Documentation - List Interface: https://docs.oracle.com/en/java/javase/9/docs/api/java/util/List.html
- Oracle Java SE 9 Documentation - ArrayList: https://docs.oracle.com/en/java/javase/9/docs/api/java/util/ArrayList.html
- JEP 269: Convenience Factory Methods for Collections: https://openjdk.org/jeps/269