一、背景
看文档时看到一段话:
list.toArray方法将列表转化为数组是浅拷贝勾起了我的好奇心
二、分析
回顾一下深浅拷贝的定义(简单参考下博客:https://blog.csdn.net/weixin_44772566/article/details/136515491):
那就意味着,如果我拷贝出来的对象修改后,会影响到原来的数组,那就意味着是浅拷贝,如果不会影响到原来的数组,那就是深拷贝
查看下list.toArray源码,发现有2个实现:
- toArray()
- 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;
}
- toArray():主要依赖Arrays.copyOf,到最底下的copyOf函数时,如果是引用类,则先去new一个新的Object数组(基本类型则也去new一个,只是实现不一样),然后再用System.arraycopy,猜测不是浅拷贝,因为new了新的对象空间
- 放入一个a数组,然后copy完了返回的依旧是a,相当于是copy到a里了,猜测是浅拷贝
猜测完成,实践开始
三、实践
3.1 实践方式
按照上述空参的list.toArray(),有2种方式,一种是基础类型,一种是引用类型
实现方式:
新建一个数组,然后调用Arrays.copyOf(elementData, size)方法
新建一个ArrayList,然后调用Arrays.copyOf(elementData, size)方法
3.2 代码如下
ArrayList.toArray()运行逻辑:
- 新建ArrayList集合
- 复制,也就是arrayList.toArray()
- 修改复制的元素和新建元素的第一个值
- 对比结果并打印,如果是浅拷贝,值一定一样
Arrays.copyOf()运行逻辑:
- 新建数组
- 复制,也就是Arrays.copyOf()
- 修改复制的元素和新建元素的第一个值
- 对比结果并打印,如果是浅拷贝,值一定一样
代码如下
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:
跟上面一样,值的内容地址是一样的,为什么Integer类型改了,但是User类型没改呢?
主要还是前面引用类型只改了里面属性,但是地址值没替换
但是包装类型,比如一个new Integer(1),你没法直接替换这个Integer里面的内容,比如说new Integer.set(),没有这样的方法,所以你就只能替换引用,所以引用再去对比就会发现地址值不一致,因为基本类型这块是直接将整个类都换了。
如果是直接用基本类型,地址如下:
如果使用基本类型赋值,也是一样的
再看看引用类:
结果都一样,其他几个没修改的地址值就没变过
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
native方法,看看注释吧:
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先复制到一个临时数组,再从临时数组将内容复制到目标数组,那整个元素方面的话,就是纯纯复制了,是浅拷贝没问题了