Java 值传递、引用传递 以及clone

从一个问题说起:有一个产品列表界面,用户可以选中想要的产品,对于选中的产品进行高亮显示。adapter 中有两个数据集集合list,一个是全部数据,一个是选中的数据(默认全选中),当我在构建这个adapter的时候,把这个集合也初始化了,当时代码是这样写的?

 public KpNewUserProductsAdapter(List<KpNewUserProduct> kpNewUserProducts) {
        mKpNewUserProducts = kpNewUserProducts;
        mKpNewUserSelectedProducts = mKpNewUserProducts;     
        //mKpNewUserSelectedProducts.addAll(mKpNewUserProducts);
    }

我把全部的数据集合直接赋值给了选中的数据集合,后面用户可以进行自己想要的产品,这个时候选中的产品list 会变化(因为用户可能只选择了部分产品),这时候就产生了bug,发现总的产品数量会随着选中的产品list改变?

这时候我想到了java中的赋值操作的含义,重新梳理了java的值传递、引用传递;还联想到了clone,这个几个知识点其实是一致的。下面来一一说解吧。

先说上面提到的问题原因:对象的赋值操作是引用传递,它们会指向同一个地址,任意一个改变会引起另一个的变化;

在这里我显然是想要一个全部产品list的副本,但赋值操作是不行的,这时候就需要clone。clone区分深浅clone,它们的区别就是被clone的对象其字段是否包含除基本类型之外的对象,如果我们只想要clone 基本类型,则是浅拷贝,如果想要连包含的对象也拷贝就是深拷贝。

参考文章:https://blog.csdn.net/qq_33314107/article/details/80271963

问题:大家有没有注意到为什么 list 集合类在clone 时,其泛型对象为什么不需要实现cloneable接口?来,研究一下。

首先集合接口类是没有继承cloneable 接口的,但是其实现类都是实现的,比如ArrayList,不信你打开源码看看。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

文章参考https://blog.csdn.net/qiumengchen12/article/details/45022919

然后我们在看看它的clone 实现
 

    /**
     * Returns a shallow copy of this <tt>ArrayList</tt> instance.  (The
     * elements themselves are not copied.)
     *
     * @return a clone of this <tt>ArrayList</tt> instance
     */
    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);
        }
    }

关键代码是 arrays.copyof(elementData, size) 实现的,而arrays.copyof 最终会调用native的copy 方法,

System.arraycopy(original, 0, copy, 0,
                 Math.min(original.length, newLength));

这个方法其实是数组的复制方法,而ArrayList 的底层数据结构也是数组,我推定这个方法不是内存引用,应该是值的复制,所以arrayList 容器内的类不需要实现cloneable 接口。这就是根本原因,之前我一直以为在clone arraylist容器时,对容器类的对象也是遍历调用clone ,显然不是的。还可以继续深追System.arraycopy()方法,探究其原理,可以到得

  • 该方法针对数据类型为基本类型的一维数组进行的是值复制;
  • 二维数组或数据为引用类型的一维数据来说,进行的是引用复制

参考文章:https://blog.csdn.net/a734797702/article/details/7604847

值传递 引用传递

传递:指的是方法进行参数传递。见名知意,方法的形参接受到的是实参的值的拷贝(值)还是地址(引用)来区分。值传递一般针对基本类型,对象是引用传递。当然引用传递会影响之前的值,其效率高;值传递和之前的参数没有关系,效率低一点。

明白这个了,我们就要注意在方法的参数传递中,应该避免使用对象,因为方法里的行为可能改变之前的值,一般这是不愿我们看到的,所以方法一般都是传递基本类型。eg:

public class Person {

    public String mString;

    public Person(String string) {
        mString = string;
    }
}

private void change(Person person){
        person.mString = "changeTo123";
}


测试

Test test = new Test();
        Person person = new Person("person");
        System.out.println("person String1:" + person.mString);
        test.change(person);
        System.out.println("person String2:" + person.mString);

打印
person String1:persion
person String2:changeTo123

值传递 引用传递其实就是对应的clone 深浅拷贝,两个话题可以联系到一起,这样理解更到位,更深刻,好了。明白了这个,有时候会解决你难以解决的bug。


参考资料:
clone:https://blog.csdn.net/qq_33314107/article/details/80271963 
值传递引用传递 https://blog.csdn.net/Norte_L/article/details/80250057

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章