深入Java集合框架:数组与List互转底层原理剖析(JDK 8)

前言

这是最近学习Java集合框架时做的一篇笔记,主要记录了数组与 ArrayList 相互转换的底层原理和一些个人理解。内容比较基础,算是对自己学习过程的一个梳理,如果有不准确的地方欢迎指正


一、数组转List

1. 使用Arrays.asList()方法

java 复制代码
public static void testArray2List() {
    String[] strs = {"aaa", "bbb", "ccc"};
    List<String> list = Arrays.asList(strs);
    for (String s : list) {
        System.out.println(s);
    }
}

源码剖析:

Arrays.asList()方法的源码实现如下:

java 复制代码
// Arrays 类中的方法
public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);// 注意:此 ArrayList 是 Arrays 的内部类
}

这里返回的ArrayList并不是我们常用的java.util.ArrayList,而是Arrays类内部的一个静态内部类:

java 复制代码
// Arrays 的内部类(不是 java.util.ArrayList!)
private static class ArrayList<E> extends AbstractList<E> {
    private final E[] a; // 直接持有传入数组的引用

    ArrayList(E[] array) {
        a = array; // 没有拷贝,直接存引用
    }

    @Override
    public E get(int index) {
        return a[index]; // 直接读原数组
    }

    @Override
    public E set(int index, E element) {
        E oldValue = a[index];
        a[index] = element; // 直接改原数组
        return oldValue;
    }

    // 没有重写 add() / remove()
}

关键点:

  • 这个内部类ArrayList直接持有了原始数组的引用(通过构造函数传入)

  • 它没有实现add()remove()等方法(会抛出UnsupportedOperationException

  • 对List的修改会直接影响原始数组

2. 转换后修改元素的影响

修改List元素:

java 复制代码
List<String> list = Arrays.asList(strs);
list.set(0, "ddd"); // 合法操作
System.out.println(strs[0]); // 输出"ddd",原始数组也被修改

修改数组元素:

java 复制代码
strs[0] = "eee";
System.out.println(list.get(0)); // 输出"eee",List也被修改

尝试添加/删除元素:

java 复制代码
list.add("fff"); // 抛出 UnsupportedOperationException
list.remove(0);  // 抛出 UnsupportedOperationException

我的理解

从源码可以看出几点:

  1. 它返回的根本不是 java.util.ArrayList ,而是 Arrays 自己的一个内部类,这个内部类直接继承自 AbstractList

  2. 内部没有拷贝任何数据 ,只是把原数组的引用存到了字段 a 里。

  3. get()set() 操作的都是这个字段 a,也就是直接操作原数组

所以本质上,Arrays.asList() 做的事情是:给原数组套了一层 List 的壳,让你可以以 List 的方式去读写原数组。

这也解释了两个常见现象:

  • **为什么不能 add / remove?**​ ------ 内部类没有重写这两个方法,调用父类 AbstractList 的默认实现会直接抛 UnsupportedOperationException;而且数组长度本身就是固定的,也没法真正"加"。

  • **为什么修改 List 元素会同步影响原数组?**​ ------ 因为本来就是同一块内存,List 内部字段 a 指向的就是你传进来的那个数组对象。

如果用一句话来概括:Arrays.asList() 返回的是一个零拷贝的数组视图,不是一个真正的动态数组。

如果你需要一个独立的、可修改的ArrayList,标准写法是:

java 复制代码
List<String> list = new ArrayList<>(Arrays.asList(array));

这样会通过 ArrayList 的构造函数把数据拷贝一份,得到一个真正属于自己的容器。


二、List转数组

1. 使用toArray()方法

java 复制代码
public static void testList2Array() {
    List<String> list = new ArrayList<String>();
    list.add("aaa");
    list.add("bbb");
    list.add("ccc");
    String[] array = list.toArray(new String[list.size()]);
    for (String s : array) {
        System.out.println(s);
    }
}

源码剖析:

ArrayList.toArray()方法的源码实现:

java 复制代码
public <T> T[] toArray(T[] a) {
    if (a.length < size)
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

Arrays.copyOf()System.arraycopy() 做的事情可以概括为:

  1. 创建一个新的数组对象(容器是新的)

  2. 把原 List 内部数组中的每个元素引用,逐个拷贝到新数组中(元素引用是旧的)

关键点:

  • toArray()方法会创建一个新的数组(即使传入的数组长度足够,也会复制元素)

  • 新数组与原List是完全独立的,互不影响

  • 传入的数组参数主要用于指定返回数组的类型

2. 转换后修改元素的影响

修改List元素:

java 复制代码
List<String> list = new ArrayList<>();
list.add("aaa");
String[] array = list.toArray(new String[0]);
list.set(0, "bbb");
System.out.println(array[0]); // 输出"aaa",数组不受影响

修改数组元素:

java 复制代码
array[0] = "ccc";
System.out.println(list.get(0)); // 输出"aaa",List不受影响

我的理解

  • 容器层面 :新数组是独立 new 出来的,所以往新数组里赋值一个新元素(array[0] = new String("X")),不会影响原 List。

  • 元素层面:拷贝的只是引用地址,如果元素是可变对象,通过任意一端修改对象的属性,另一端也会看到变化。这就是浅拷贝的语义。

基本类型数组(int[]long[] 等)因为直接拷贝值,不存在引用共享的问题,天然就是完全独立的。


三、完整示例与验证

java 复制代码
import java.util.*;

public class ArrayToListConversion {
    
    // 数组转List
    public static void testArray2List() {
        System.out.println("=== 数组转List ===");
        String[] strs = {"aaa", "bbb", "ccc"};
        List<String> list = Arrays.asList(strs);
        
        // 打印原始数组
        System.out.println("原始数组: " + Arrays.toString(strs));
        
        // 修改List元素
        list.set(0, "ddd");
        System.out.println("修改List后数组: " + Arrays.toString(strs));
        
        // 修改数组元素
        strs[1] = "eee";
        System.out.println("修改数组后List: " + list);
        
        // 尝试添加元素
        try {
            list.add("fff");
        } catch (UnsupportedOperationException e) {
            System.out.println("添加元素失败: " + e.getMessage());
        }
    }
    
    // List转数组
    public static void testList2Array() {
        System.out.println("\n=== List转数组 ===");
        List<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        
        // 转换为数组
        String[] array = list.toArray(new String[0]);
        
        // 打印原始List
        System.out.println("原始List: " + list);
        
        // 修改List元素
        list.set(0, "ddd");
        System.out.println("修改List后数组: " + Arrays.toString(array));
        
        // 修改数组元素
        array[1] = "eee";
        System.out.println("修改数组后List: " + list);
    }
    
    public static void main(String[] args) {
        testArray2List();
        testList2Array();
    }
}

输出结果:

bash 复制代码
=== 数组转List ===
原始数组: [aaa, bbb, ccc]
修改List后数组: [ddd, bbb, ccc]
修改数组后List: [ddd, eee, ccc]
添加元素失败: Unsupported operation

=== List转数组 ===
原始List: [aaa, bbb, ccc]
修改List后数组: [aaa, bbb, ccc]
修改数组后List: [ddd, bbb, ccc]

四、关键结论

  1. 数组转List (Arrays.asList())

    • 返回的List是原始数组的"视图",共享同一内存空间

    • 对List的修改会直接影响原始数组

    • 对数组的修改也会影响List

    • 不支持添加/删除操作(会抛出UnsupportedOperationException

  2. List转数组 (toArray())

    • 返回一个全新的数组,与原始List完全独立

    • 对List的修改不会影响数组

    • 对数组的修改也不会影响List

    • 推荐使用list.toArray(new T[0])方式(Java 6+中性能更好)

  3. 使用建议

    • 如果需要一个可修改大小的List,应将Arrays.asList()的结果包装进new ArrayList<>()

    • 如果需要完全独立的数组,应使用toArray()方法

    • 在处理基本类型数组时需特别注意(基本类型数组转List有特殊处理)

理解这些转换机制和相互影响关系,对于编写健壮的Java代码非常重要,特别是在处理集合和数组混合使用的场景时。

写在最后

以上就是这次关于数组与 ArrayList 转换的学习记录。后续我还会继续整理深浅拷贝、基本类型数组转换等其他相关话题,慢慢补全这个系列。如果文中有理解偏差的地方,欢迎大家在评论区交流指正~

相关推荐
Java成神之路-1 小时前
深入理解 HashMap 扩容时机:JDK 7 vs JDK 8 的设计差异与取舍
java·juc
竹枝溪1 小时前
MySQL进阶:约束、多表设计、多表查询与事务
java·数据库·mysql·事务·子查询·acid·多表查询
上玄code1 小时前
【Agent精讲】调一个LLMAPI背后的工程问题
java·开发语言·python·chatgpt
李冰然1 小时前
深入Java集合框架:ArrayList源码解析(JDK 8)
java
小小啊python1 小时前
IDEA中SpringBoot项目配置热部署
java·ide·intellij-idea·springboot·热部署
Java成神之路-2 小时前
Spring AI Alibaba 实现多轮对话记忆:ChatMemory 与 Redis 持久化实战
java·springaialibaba
JAVA面经实录9172 小时前
网络编程基础(Java Web/分布式前置·完整版)(十一)
java·前端·网络
代码方舟2 小时前
零信任架构实战:基于天远人企关联构建自动化企业尽职调查网关
java·人工智能·架构·自动化
Escalating_xu2 小时前
【Linux】基础 I/O 深度解析:FILE、文件描述符、open/read/write、重定向与缓冲区
java·linux·服务器