今天跟大家分享从ArrayList说起复制与参数传递机制
的
知识。
0
前言
这两者都算是java基础中的基础,平常写代码可能并没有过多的去深究它,但这样容易引发一些不可预知的BUG。
这里有一个简单的类,文章中会提到多次。
一个学生类,它有两个属性,String类型的name与Integer类型的age。
public class Student {
private String name;
private Integer age;
public Student() {
}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
首先我们有一个List。
ArrayList originalList = new ArrayList();
list1.add(new Student("五更琉璃", 15));
list1.add(new Student("高坂桐乃", 14));
1
最简单粗暴的复制
ArrayList copyList = new ArrayList<>();
copyList = originalList;
copyList.set(1,new Student("土间埋",16));
copyList直接获得originalList的引用
originalList ==>
Student{name='五更琉璃', age=15}
Student{name='土间埋', age=16}
copyList ==>
Student{name='五更琉璃', age=15}
Student{name='土间埋', age=16}
结果如下,我们发现,即使只改变了copyList的元素element,原来的ArrayList也跟着变了。
originalList = {ArrayList@531} size = 2
0 = {Student@533} "Student{name='五更琉璃', age=15}"
1 = {Student@534} "Student{name='土间埋', age=16}"
copyList = {ArrayList@531} size = 2
0 = {Student@533} "Student{name='五更琉璃', age=15}"
1 = {Student@534} "Student{name='土间埋', age=16}"
打个断点我们可以发现,无论是ArrayList,还是里面的引用,它们的内存地址是完全一样的。
也就是说,直接赋值 copyList = originalList;的这种方法,很难称得上是一种复制。
2
使用clone方法进行复制
ArrayList copyList = (ArrayList) originalList.clone();
copyList.set(1,new Student("土间埋",16));
结果如下:
看起来正常了,我们的originalList并没有因为copyList的set方法而改变。
originalList ==>
Student{name='五更琉璃', age=15}
Student{name='高坂桐乃', age=14}
copyList ==>
Student{name='五更琉璃', age=15}
Student{name='土间埋', age=16}
然而打开断点会发现,不是那么一回事。
我们发现,originalList和copyList的指向确实不同了。
然而List中的元素,指向的还是同一块地址。
originalList = {ArrayList@531} size = 2
0 = {Student@534} "Student{name='五更琉璃', age=15}"
1 = {Student@535} "Student{name='高坂桐乃', age=14}"
copyList = {ArrayList@532} size = 2
0 = {Student@534} "Student{name='五更琉璃', age=15}"
1 = {Student@539} "Student{name='土间埋', age=16}"
也就是说,如果我们不进行set重新修改元素的指向,而是直接改变元素内的属性。
ArrayList copyList = (ArrayList) originalList.clone();
copyList.get(1).setName("土间埋");
copyList.get(1).setAge(16);
从断点中我们可以得到我们的预期。
originalList 中的第一个元素,也会被改变。
originalList = {ArrayList@463} size = 2
0 = {Student@466} "Student{name='五更琉璃', age=15}"
1 = {Student@467} "Student{name='土间埋', age=16}"
copyList = {ArrayList@464} size = 2
0 = {Student@466} "Student{name='五更琉璃', age=15}"
1 = {Student@467} "Student{name='土间埋', age=16}"
从这里可以看出,ArrayList提供的clone方法,实际上是一种浅复制。
也就是它不是一种递归复制。
它只是改变了顶层,copyList 的引用。
我们看看源码
public Object clone() {
try {
ArrayList> v = (ArrayList>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
源码中,先是clone了一个新的ArrayList:
v
然后将原ArrayList中的数据直接复制到了新的ArrayList中。
再看看ArrayList中的add与addAll方法,实际上也是如出一辙。
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
public boolean addAll(Collection extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew);
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
它们只是把元素的引用复制了一遍。
也就是说调用clone、add、addAll方法,当改变元素中的属性时,新List中的元素也会跟着改变。
java的参数传递也是如此,java中,参数的传递都是值传递。
当一个对象实例作为一个参数被传递到方法中时,参数的值就是该对象的引用一个副本。
当这个副本的属性在方法内部被改变时,这个副本的正本也会被改变。