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