从一个问题说起:有一个产品列表界面,用户可以选中想要的产品,对于选中的产品进行高亮显示。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