从ArrayList说起的JAVA复制与参数传递机制

这两者都算是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<Student> originalList = new ArrayList();
        list1.add(new Student("五更琉璃", 15));
        list1.add(new Student("高坂桐乃", 14));

1、先说说最简单粗暴的复制。

        ArrayList<Student> 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<Student> copyList = (ArrayList<Student>) 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<Student> copyList = (ArrayList<Student>) 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) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

源码中,先是clone了一个新的ArrayList:v

然后将原ArrayList中的数据直接复制到了新的ArrayList中。

再看看ArrayList中的add与addAll方法,实际上也是如出一辙。

   public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

它们只是把元素的引用复制了一遍。也就是说调用clone、add、addAll方法,当改变元素中的属性时,新List中的元素也会跟着改变。

java的参数传递也是如此,java中,参数的传递都是值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是该对象的引用一个副本。

当这个副本的属性在方法内部被改变时,这个副本的正本也会被改变。

那么就针对当前业务来说,如何实现深克隆?

        ArrayList<Student> copyList = new ArrayList<>();
        for (Student student : originalList) {
            copyList.add(student.clone());
        }

        copyList.get(1).setName("土间埋");
        copyList.get(1).setAge(16);

粗暴实现:我们发现它们的地址已经完全不同了。

originalList = {ArrayList@464}  size = 2
 0 = {Student@467} "Student{name='五更琉璃', age=15}"
 1 = {Student@468} "Student{name='高坂桐乃', age=14}"
copyList = {ArrayList@465}  size = 2
 0 = {Student@472} "Student{name='五更琉璃', age=15}"
 1 = {Student@473} "Student{name='土间埋', age=16}"

2018年7月17日 补充,偶然回顾这篇文章,其实还有一种又快又好的深复制方法:序列化,将对象转成进行序列化再反序列化即可,效率还是挺高的(不适用于复杂对象)..

猜你喜欢

转载自blog.csdn.net/anurnomeru/article/details/79621794
今日推荐