Arrays.asList 使用总结文档
1. 问题背景
Arrays.asList 返回的 ArrayList 并不是 java.util.ArrayList,而是 java.util.Arrays 的一个内部类:
```text
java.util.Arrays$ArrayList
```
它和 java.util.ArrayList 都实现了 List 接口,但二者没有继承关系。
因此下面写法会编译错误:
```java
ArrayList<String> list = Arrays.asList("a", "b"); // 编译错误
```
即使强转,也会运行时异常:
```java
ArrayList<String> list =
(ArrayList<String>) Arrays.asList("a", "b"); // ClassCastException
```
正确写法是用 List 接:
```java
List<String> list = Arrays.asList("a", "b");
```
---
2. 为什么不能用 ArrayList 接?
看 Arrays.asList 的方法签名:
```java
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a); // 这里的 ArrayList 是 Arrays 的内部类
}
```
它声明返回的是 List<T>,实际对象是 Arrays$ArrayList。
源码简化如下:
```java
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable {
private final E[] a;
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
public int size() {
return a.length;
}
public E get(int index) {
return a[index];
}
public E set(int index, E element) {
E old = a[index];
a[index] = element;
return old;
}
// 没有实现 add/remove,所以长度固定
}
```
所以:
· Arrays.asList 返回类型是 List;
· 实际类型是 Arrays$ArrayList;
· 它不是 java.util.ArrayList;
· 不能用 ArrayList 接,也不能强转。
---
3. 为什么 JDK 要设计成内部类?
因为 Arrays.asList 的语义不是“创建一个新的可变 ArrayList”,而是:
把数组包装成一个 List 视图。
这样设计的好处:
· 不复制数组,零拷贝;
· 性能好,省内存;
· 可以直接读写原数组;
· 长度固定,不需要支持扩容;
· 对外只暴露 List 接口,隐藏内部实现。
示例:
```java
String[] arr = {"a", "b"};
List<String> list = Arrays.asList(arr);
list.set(0, "x");
System.out.println(arr[0]); // x,原数组被修改
list.add("c"); // UnsupportedOperationException,长度固定
```
---
4. Arrays.asList 和 new ArrayList<>(...) 的区别
对比项 Arrays.asList(arr) new ArrayList<>(Arrays.asList(arr))
底层数据 直接引用原数组 复制出一个新数组
长度 固定,不能变 可变,可扩容
add / remove / clear 抛 UnsupportedOperationException 正常支持
set 支持,会改原数组 支持,不会改原数组
改原数组槽位 会影响这个 List 不影响这个 List
改 List 元素 set 会影响原数组 不影响原数组
内存 不复制,轻量 复制一份,额外内存
类型 Arrays$ArrayList 真正的 java.util.ArrayList
常见用途 只读、固定长度、包装数组 需要增删改、独立集合
示例:
```java
String[] arr = {"a", "b"};
List<String> view = Arrays.asList(arr);
List<String> copy = new ArrayList<>(view);
// 1. set 的区别
view.set(0, "x");
System.out.println(Arrays.toString(arr)); // [x, b]
System.out.println(copy); // [a, b]
// 2. 改原数组槽位的区别
arr[1] = "y";
System.out.println(view); // [x, y]
System.out.println(copy); // [a, b]
// 3. 增删的区别
copy.add("c"); // 正常
view.add("c"); // UnsupportedOperationException
```
---
5. 视图 vs 副本
Arrays.asList 返回的是原数组的视图:
```text
原数组 arr: [0] -> "a" [1] -> "b"
view 内部: 直接就是 arr
```
new ArrayList<>(Arrays.asList(arr)) 返回的是副本:
```text
原数组 arr: [0] -> "a" [1] -> "b"
copy 内部数组: [0] -> "a" [1] -> "b"
```
执行:
```java
arr[1] = "y";
```
结果:
```text
原数组 arr: [0] -> "a" [1] -> "y"
copy 内部数组: [0] -> "a" [1] -> "b"
```
所以 view 会变,copy 不变。
---
6. 复制是浅拷贝
new ArrayList<>(...) 复制的是“元素引用的值”,不是把对象也克隆一份。
```java
class User {
String name;
User(String name) {
this.name = name;
}
void setName(String name) {
this.name = name;
}
public String toString() {
return name;
}
}
```
```java
User[] arr = { new User("张三") };
List<User> copy = new ArrayList<>(Arrays.asList(arr));
// 情况 1:修改对象内部状态
arr[0].setName("李四");
System.out.println(copy.get(0)); // 李四
// 情况 2:替换原数组槽位
arr[0] = new User("王五");
System.out.println(copy.get(0)); // 李四
```
图示:
```text
原数组 arr: [0] -> User对象A
copy 内部数组: [0] -> User对象A
```
· arr[0].setName("李四"):改的是 User对象A,两个 List 都看得到;
· arr[0] = new User("王五"):改的是 arr 的槽位,copy 的槽位没变。
总结:
new ArrayList<>(Arrays.asList(arr)) 是浅拷贝:
新集合、新内部数组,但元素引用相同。
替换原数组槽位,副本不变;修改对象内部状态,副本会变。
---
7. 生产中常见坑
7.1 以为能 add,结果运行时报错
```java
List<String> list = Arrays.asList("a", "b");
list.add("c"); // UnsupportedOperationException
```
编译没问题,运行才崩。
7.2 方法返回内部数组的视图,破坏封装
```java
public List<String> getNames() {
return Arrays.asList(names); // names 是内部数组
}
```
外部拿到后:
```java
list.set(0, "hacked");
```
内部数组就被改了。
正确做法是返回副本:
```java
public List<String> getNames() {
return new ArrayList<>(Arrays.asList(names)); // 可变副本
}
```
如果只读:
```java
public List<String> getNames() {
return List.copyOf(Arrays.asList(names)); // Java 10+,不可变副本
}
```
7.3 基本类型数组的坑
```java
int[] arr = {1, 2, 3};
List<int[]> list = Arrays.asList(arr);
System.out.println(list.size()); // 1,不是 3
```
因为 Arrays.asList 把整个 int[] 当成一个对象。
想要 List<Integer>:
```java
List<Integer> list = Arrays.stream(arr)
.boxed()
.collect(Collectors.toList());
```
7.4 框架或 API 要求 ArrayList
```java
void process(ArrayList<String> list) { ... }
process(Arrays.asList("a", "b")); // 编译错误
```
因为 Arrays.asList 返回 List,实际类型不是 java.util.ArrayList。
需要自己复制:
```java
process(new ArrayList<>(Arrays.asList("a", "b")));
```
---
8. 如何选择
需求 推荐写法
只读、固定长度 List.of("a", "b")(Jav