1. 概述
本文将系统梳理在 Java 中如何用一行代码完成 List
的初始化。这类写法在日常开发中非常常见,尤其适合用于测试数据构造、常量定义等场景。
虽然实现方式多样,但每种都有其适用边界和“踩坑”点。本文会结合代码示例,帮你避开那些看似简单实则容易翻车的陷阱。
2. 通过数组创建 List
Java 提供了 Arrays.asList()
方法,可以将数组快速转为 List
,结合数组字面量,实现单行初始化:
List<String> list = Arrays.asList("foo", "bar");
✅ 这种写法简洁直观,利用了 Java 的 varargs(可变参数)机制,无需显式创建数组。
⚠️ 但要注意:**Arrays.asList()
返回的并不是 ArrayList
或 LinkedList
,而是一个由原始数组支撑的特殊 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, 3
是int
字面量,自动装箱为Integer
Arrays.asList(1, 2, 3)
返回的是List<Integer>
- 虽然
int
可以自动转long
,但Integer
和Long
是不同的包装类,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(...))
更节省内存
⚠️ 缺点也很明确:一旦创建,无法修改。任何 add
、remove
操作都会抛出 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)
⚠️ 为什么不推荐?
- 内存泄漏风险:匿名内部类隐式持有外部类引用,可能导致外部类无法被 GC
- 性能开销:每次调用都会生成一个新的类(
.class
文件),增加类加载负担 - 破坏封装性:相当于创建了一个“临时子类”,不符合设计原则
- 序列化问题:包含非静态内部类,序列化时可能出错
✅ 结论:这是一种反模式(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