1. 概述

本文将系统梳理在 Java 中如何用一行代码完成 List 的初始化。这类写法在日常开发中非常常见,尤其适合用于测试数据构造、常量定义等场景。

虽然实现方式多样,但每种都有其适用边界和“踩坑”点。本文会结合代码示例,帮你避开那些看似简单实则容易翻车的陷阱。

2. 通过数组创建 List

Java 提供了 Arrays.asList() 方法,可以将数组快速转为 List,结合数组字面量,实现单行初始化:

List<String> list = Arrays.asList("foo", "bar");

✅ 这种写法简洁直观,利用了 Java 的 varargs(可变参数)机制,无需显式创建数组。

⚠️ 但要注意:**Arrays.asList() 返回的并不是 ArrayListLinkedList,而是一个由原始数组支撑的特殊 List 实现**(内部类 Arrays.ArrayList,注意包名是 java.util.Arrays)。

这意味着它有两个关键限制:

2.1 固定大小(不可变长度)

List 不支持添加或删除元素,否则会抛出 UnsupportedOperationException

@Test(expected = UnsupportedOperationException.class)
public void givenArraysAsList_whenAdd_thenUnsupportedException() {
    List<String> list = Arrays.asList("foo", "bar");
    list.add("baz"); // ❌ 抛出异常
}

解决方案:将其作为构造参数传给真正的 ArrayList,实现“深拷贝”:

List<String> list = new ArrayList<>(Arrays.asList("foo", "bar"));
list.add("baz"); // ✅ 成功
assertEquals(List.of("foo", "bar", "baz"), list);

2.2 引用共享

原始数组和 List 共享元素引用。修改数组会影响 List,反之亦然:

@Test
public void givenArraysAsList_whenCreated_thenShareReference(){
    String[] array = {"foo", "bar"};
    List<String> list = Arrays.asList(array);
    array[0] = "baz";
    
    assertEquals("baz", list.get(0)); // ✅ 断言通过
}

⚠️ 这意味着如果你修改了原始数组,List 内容也会变,容易引发隐蔽 bug。

2.3 常见踩坑:初始化 List<Long>

初学者常犯的一个错误是:

// ❌ 编译失败!
List<Long> listOfLong = new ArrayList<Long>(Arrays.asList(1, 2, 3));

报错信息:

java: no suitable constructor found for ArrayList(java.util.List<java.lang.Integer>)

原因如下:

  • 1, 2, 3int 字面量,自动装箱为 Integer
  • Arrays.asList(1, 2, 3) 返回的是 List<Integer>
  • 虽然 int 可以自动转 long,但 IntegerLong 是不同的包装类,Java 不会自动转换 List<Integer>List<Long>

正确做法:使用 long 字面量(加 L 后缀):

List<Long> listOfLong = new ArrayList<>(Arrays.asList(1L, 2L, 3L));

List<Long> expected = List.of(1L, 2L, 3L);
assertEquals(expected, listOfLong);

这样 Arrays.asList(1L, 2L, 3L) 返回的就是 List<Long>,类型完全匹配。

3. 使用 Stream(Java 8+)

Java 8 引入 Stream 后,可以通过 Stream.of() 快速构建并收集为 List

@Test
public void givenStream_thenInitializeList(){
    List<String> list = Stream.of("foo", "bar")
        .collect(Collectors.toList());
    
    assertTrue(list.contains("foo"));
}

⚠️ 但要注意:Collectors.toList() 不保证返回的具体实现类型,也不保证其可变性、序列化或线程安全性。

官方文档明确指出:不要依赖返回 List 的具体行为

✅ 虽然性能上可能略逊于 Arrays.asList()(涉及 Stream 管道开销),但在绝大多数业务场景下,这种差异可以忽略不计,属于“过早优化”。

4. 工厂方法(Java 9+)

JDK 9 引入了集合的不可变工厂方法,极大简化了初始化:

List<String> list = List.of("foo", "bar", "baz");
Set<String> set = Set.of("foo", "bar", "baz");

✅ 优点非常明显:

  • ✅ 语法极简
  • ✅ 返回的 List不可变的(immutable),天然线程安全
  • ✅ 内部实现做了空间优化,比 new ArrayList<>(Arrays.asList(...)) 更节省内存

⚠️ 缺点也很明确:一旦创建,无法修改。任何 addremove 操作都会抛出 UnsupportedOperationException

📌 推荐在定义常量、配置项或需要安全共享的场景中优先使用 List.of()

5. 双大括号初始化(Double-Brace Initialization)

你可能见过这种写法:

@Test
public void givenAnonymousInnerClass_thenInitialiseList() {
    List<String> cities = new ArrayList<String>() {{
        add("New York");
        add("Rio");
        add("Tokyo");
    }};
    
    assertTrue(cities.contains("New York"));
}

这种语法被称为“双大括号初始化”,看起来很优雅,但实际上存在严重问题:

  • 第一个 {:声明一个匿名内部类(继承 ArrayList
  • 第二个 {:定义一个实例初始化块(instance initializer block)

⚠️ 为什么不推荐?

  1. 内存泄漏风险:匿名内部类隐式持有外部类引用,可能导致外部类无法被 GC
  2. 性能开销:每次调用都会生成一个新的类(.class 文件),增加类加载负担
  3. 破坏封装性:相当于创建了一个“临时子类”,不符合设计原则
  4. 序列化问题:包含非静态内部类,序列化时可能出错

✅ 结论:这是一种反模式(anti-pattern),虽然写起来爽,但代价太高,建议彻底避免。

6. 总结

方法 是否可变 Java 版本 推荐度
Arrays.asList() ❌ 固定大小 所有 ⚠️ 谨慎使用
new ArrayList<>(Arrays.asList()) ✅ 可变 所有 ✅ 通用方案
Stream.of().collect() 取决于实现 8+ ✅ 可接受
List.of() ❌ 不可变 9+ 首选(不可变场景)
双大括号 ✅ 可变 所有 禁止使用

📌 最终建议

  • 如果你用的是 **Java 9+**,优先使用 List.of() 初始化不可变列表
  • 需要可变列表时,使用 new ArrayList<>(Arrays.asList(...)) 最稳妥
  • 避免双大括号初始化,哪怕它看起来“简单粗暴”

所有示例代码已上传至 GitHub:https://github.com/baeldung/core-java-modules/tree/master/core-java-collections-list-2


原始标题:Java List Initialization in One Line | Baeldung