Java 9 中 List.of 方法与传统 ArrayList 初始化方式有什么区别?

文章导读
在 Java 开发中,列表初始化看似简单,但选错方式可能导致线上运行时报错。如果你需要创建一个后续不会修改的常量列表,直接用 Java 9 引入的 List.of() 最省事;如果需要往列表里增删元素,或者数据里可能包含 null,请继续使用 new ArrayList<>(Arrays.asList(...)) 或常规 add 方式,避免运行时报错。
📋 目录
  1. 核心机制差异
  2. 完整可运行代码示例
  3. 异常堆栈与排查
  4. 迁移与避坑指南
  5. 参考来源
A A

在 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(),它虽然看起来像列表,但它只是原数组的视图,大小固定,修改元素会影响原数组,且不允许增删。这是一个常见的隐蔽风险点。

Java 9 中 List.of 方法与传统 ArrayList 初始化方式有什么区别?

完整可运行代码示例

以下是一个完整的 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. 尝试修改不可变列表:

Java 9 中 List.of 方法与传统 ArrayList 初始化方式有什么区别?
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.ImmutableCollectionsList.of 相关的栈帧,即可确认是使用了不可变集合导致的错误。

迁移与避坑指南

在老项目升级 Java 9+ 或重构代码时,请遵循以下步骤:

  1. 确认需求:列表创建后是否需要 add、remove 或 set 操作?是否需要存 null 值?
  2. 选择 API:如果不需要修改且无 null,优先 List.of() 代码更简洁;否则用 new ArrayList<>() 包装。
  3. 注意版本:List.of() 需要 Java 9 及以上环境,老项目升级需谨慎。
  4. 警惕直接替换:将旧代码中的 new ArrayList<>(Arrays.asList(...)) 直接改为 List.of(),若后续有修改操作或含 null,上线必崩。
  5. 数据源清洗:List.of() 对参数逐个判空,数据源不干净时容易抛 NullPointerException,建议在调用前过滤 null。

参考来源