從一個問題說起:有一個產品列表界面,用戶可以選中想要的產品,對於選中的產品進行高亮顯示。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