Array.copyOf() 深浅拷贝问题

一、背景

看文档时看到一段话:
在这里插入图片描述

list.toArray方法将列表转化为数组是浅拷贝勾起了我的好奇心

二、分析

回顾一下深浅拷贝的定义(简单参考下博客:https://blog.csdn.net/weixin_44772566/article/details/136515491):
在这里插入图片描述

那就意味着,如果我拷贝出来的对象修改后,会影响到原来的数组,那就意味着是浅拷贝,如果不会影响到原来的数组,那就是深拷贝
查看下list.toArray源码,发现有2个实现:

  1. toArray()
  2. toArray(T[] a)

源码如下:

    /**
    * 无参
    */
    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }
    public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }


    // 有参
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }
  1. toArray():主要依赖Arrays.copyOf,到最底下的copyOf函数时,如果是引用类,则先去new一个新的Object数组(基本类型则也去new一个,只是实现不一样),然后再用System.arraycopy,猜测不是浅拷贝,因为new了新的对象空间
  2. 放入一个a数组,然后copy完了返回的依旧是a,相当于是copy到a里了,猜测是浅拷贝

猜测完成,实践开始

三、实践

3.1 实践方式

按照上述空参的list.toArray(),有2种方式,一种是基础类型,一种是引用类型
实现方式:
新建一个数组,然后调用Arrays.copyOf(elementData, size)方法
新建一个ArrayList,然后调用Arrays.copyOf(elementData, size)方法

3.2 代码如下

ArrayList.toArray()运行逻辑:

  1. 新建ArrayList集合
  2. 复制,也就是arrayList.toArray()
  3. 修改复制的元素和新建元素的第一个值
  4. 对比结果并打印,如果是浅拷贝,值一定一样

Arrays.copyOf()运行逻辑:

  1. 新建数组
  2. 复制,也就是Arrays.copyOf()
  3. 修改复制的元素和新建元素的第一个值
  4. 对比结果并打印,如果是浅拷贝,值一定一样

代码如下

public class Main {

    private static void testIntArray() {
        ArrayList<Integer> list = new ArrayList<Integer>(Arrays.asList(new Integer(1111), new Integer(2222), new Integer(3333)));
        Object[] copyList = list.toArray();
        System.out.println("第一轮:" + list + "=" + list.get(0) + "\t\t" + copyList + "=" + copyList[0]);
        // 修改
        list.set(0, new Integer(4444));
        copyList[0] = new Integer(5555);
//        list.set(0, 4444);
//        copyList[0] = 5555;
        System.out.println("第二轮:" + list + "=" + list.get(0) + "\t" + copyList + "=" + copyList[0]);
        if (list.get(0) == copyList[0])
            System.out.println("【包装类数组的toArray()是浅拷贝】");
        else
            System.out.println("【包装类数组的toArray()是深拷贝】");
    }

    private static void testObjArray() {
        ArrayList<User> list = new ArrayList<User>(
                Arrays.asList(new User(1, "张三"), new User(2, "李四"), new User(3, "王五"))
        );
        Object[] copyList = list.toArray();
        System.out.println("第一轮:" + list + "=" + list.get(0).name + "\t" + copyList + "=" + ((User) copyList[0]).name);
        // 修改
        User list_user = list.get(0);
        list_user.name = "张三-新";
        User objUser = (User) copyList[0];
        objUser.name = "张三-心心";
        System.out.println("第二轮:" + list + "=" + list.get(0).name + "\t" + copyList + "=" + ((User) copyList[0]).name);
        if (list.get(0) == ((User) copyList[0]))
            System.out.println("【对象数组的toArray()是浅拷贝】");
        else
            System.out.println("【对象数组的toArray()是深拷贝】");
    }

    private static void testIntArraysCopyOf() {
        int[] array = {1111, 2222, 3333};
        int[] copyArray = Arrays.copyOf(array, 3);
        System.out.println("第一轮:" + array + "=" + array[0] + "\t" + copyArray + "=" + copyArray[0]);
        array[0] = 4444;
        copyArray[0] = 5555;
        System.out.println("第二轮:" + array + "=" + array[0] + "\t" + copyArray + "=" + copyArray[0]);
        if (array[0] == copyArray[0])
            System.out.println("【基础数组的Arrays.copyOf()是浅拷贝】");
        else
            System.out.println("【基础数组的Arrays.copyOf()是深拷贝】");
    }

    private static void testObjArraysCopyOf() {
        User[] array = {new User(1, "张三"), new User(2, "李四"), new User(3, "王五")};
        User[] copyArray = Arrays.copyOf(array, 3);
        System.out.println("第一轮:" + array + "=" + array[0].name + "\t" + copyArray + "=" + copyArray[0].name);
        array[0].name = "张三-新";
        copyArray[0].name = "张三-心心";
        System.out.println("第二轮:" + array + "=" + array[0] + "\t" + copyArray + "=" + copyArray[0]);
        if (array[0] == copyArray[0])
            System.out.println("【对象数组的Arrays.copyOf()是浅拷贝】");
        else
            System.out.println("【对象数组的Arrays.copyOf()是深拷贝】");
    }

    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        testIntArray();
        testObjArray();
        testIntArraysCopyOf();
        testObjArraysCopyOf();
    }

    static class User {
        Integer id;
        String name;

        public User() {
        }

        public User(Integer id, String name) {
            this.id = id;
            this.name = name;
        }
    }

}

运行结果:

第一轮:[1, 2, 3]=1		[Ljava.lang.Object;@60e53b93=1
第二轮:[-1, 2, 3]=-1	[Ljava.lang.Object;@60e53b93=-2
【包装类数组的toArray()是深拷贝】

第一轮:[Main$User@5e2de80c, Main$User@1d44bcfa, Main$User@266474c2]=张三	[Ljava.lang.Object;@6f94fa3e=张三
第二轮:[Main$User@5e2de80c, Main$User@1d44bcfa, Main$User@266474c2]=张三-心心	[Ljava.lang.Object;@6f94fa3e=张三-心心
【对象数组的toArray()是浅拷贝】

第一轮:[I@5e481248=1	[I@66d3c617=1
第二轮:[I@5e481248=-1	[I@66d3c617=-2
【对象数组的Arrays.copyOf是深拷贝】

第一轮:[LMain$User;@63947c6b=张三	[LMain$User;@2b193f2d=张三
第二轮:[LMain$User;@63947c6b=Main$User@355da254	[LMain$User;@2b193f2d=Main$User@355da254
【对象数组的Arrays.copyOf是深拷贝】

四、结论

方法集合类型结果
arraylist.toArray()包装类型深拷贝
arraylist.toArray()对象类型浅拷贝
Arrays.copyOf()基础类型深拷贝
Arrays.copyOf()对象类型浅拷贝

可以看到,对象类型的都是浅拷贝,包装类型直接去 == 对比,基础类型的话,由于你没法直接set值,所以它一定是替换地址的

附 - 反思

但是其实又有个问题,仔细看toArray()代码:

    transient Object[] elementData; // non-private to simplify nested class access
    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

也就是说,toArray的时候一直都是一个Object数组,无论你传入的是包装类还是对象类型
而后面则会根据类型来判断如何创建对象,那如果都是Object[],为什么包装类型和对象类型结果不一样呢?
我们来debug一下
包装类型的ArrayList:在这里插入图片描述

确定是Object数组,走下面,地址从561变成563,但是其他3个数字地址都没变,那么理论上来说应该就是浅拷贝

引用类型ArrayList:
image.png
跟上面一样,值的内容地址是一样的,为什么Integer类型改了,但是User类型没改呢?
主要还是前面引用类型只改了里面属性,但是地址值没替换
但是包装类型,比如一个new Integer(1),你没法直接替换这个Integer里面的内容,比如说new Integer.set(),没有这样的方法,所以你就只能替换引用,所以引用再去对比就会发现地址值不一致,因为基本类型这块是直接将整个类都换了。

如果是直接用基本类型,地址如下:
image.png
如果使用基本类型赋值,也是一样的
image.png
再看看引用类:
image.png
结果都一样,其他几个没修改的地址值就没变过

    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

native方法,看看注释吧:
image.png
If the src and dest arguments refer to the same array object, then the copying is performed as if the components at positions srcPos through srcPos+length-1 were first copied to a temporary array with length components and then the contents of the temporary array were copied into positions destPos through destPos+length-1 of the destination array.
百度翻译一下:
如果src和dest参数引用相同的数组对象,则执行复制时,将位置srcPos到srcPos+length-1的组件首先复制到具有长度组件的临时数组,然后将临时数组的内容复制到目标数组的位置destPos到destPos+length-1。

也就是说,会将数组里的components先复制到一个临时数组,再从临时数组将内容复制到目标数组,那整个元素方面的话,就是纯纯复制了,是浅拷贝没问题了

  • 26
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值