從一個問題說起:有一個産品清單界面,使用者可以選中想要的産品,對于選中的産品進行高亮顯示。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