天天看點

《資料結構和Java集合架構第三版》讀書筆記(五)淺複制(shallow copy)和深複制

今天學習ArrayList

一,淺複制

它的複制構造函數和clone()函數均為淺複制(shallow copy),即複制對象的引用。相反的深複制,則是複制對象的本身。

1,複制構造函數 ArrayList (Collection<? extends E> c)

得到了一個新的ArrayList對象,它包含了對c中元素的複制。但是注意,集合c的元素本質是引用,而不是對象。被引用的對象本身并沒有被複制。

2,clone()函數也是如此

例子:

ArrayList<StringBuffer> temp1=new ArrayList<StringBuffer>();
StringBuffer x=new StringBuffer("yes");
temp1.add(x);
ArrayList<StringBuffer> temp2=(ArrayList)temp1.clone();
temp2.set(0,x.append("no"));
           

temp2中第0個元素引用的StringBuffer對象修改為“yesno”,它和temp1中第0個元素引用的是同一個StringBuffer對象 ,即是兩個元素指向的還是同一記憶體。是以temp1和temp2均儲存了“yesno”

但是

ArrayList<String> temp1=new ArrayList();
String x="yes";temp1.add(x);ArrayList<String>  temp2=(ArrayList)temp1.clone();
temp2.set(0,"no");
           

StringBuffer對象可變,而String對象不可變。一旦建立了String對象,該對象中的内容就不能改變了。是以第二段程式中temp2的第0個元素其實是指向了新的String對象“no”,而temp1的第0個元素仍然引用“yes”

3, java.util.list.addAll()方法同樣是淺複制

4,下邊這個周遊list逐個元素add的方法也是淺複制,這個比較容易搞錯

class Person implements Serializable{
	private int age;
	private String name;
	
	public Person(){};
	public Person(int age,String name){
		this.age=age;
		this.name=name;
	}
	
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	public String toString(){
		return this.name+"-->"+this.age;
	}
	
}
           
List<Person> destList=new ArrayList<Person>(srcList.size());
for(Person p : srcList){
	destList.add(p);
}
		
printList(destList);
srcList.get(0).setAge(100);
printList(destList);
           

s rcList的第0個元素存儲的Person對象的值是可改變的。類似于StringBuffer

5,Collections的copy(List desc,List src)方法也是淺複制。下邊這篇文章的說法是錯誤的由java中深度複制一伸出Collections.copy的使用

文章稱之為深拷貝,說兩個list的每個元素所指向的不是同一記憶體。 代碼如下:

List<String> src = new ArrayList<String>();
  src.add("111");
  src.add("222");
  src.add("333");
  src.add("444");
  List<String> dic = new ArrayList<String>(Arrays.asList(new String[src
    .size()]));
  Collections.copy(dic, src);
           

注意:必須用Arrays.asList方法  

public static <T> List<T> asList(T... a)傳回一個受指定數組支援的固定大小的清單。      

如果如下邊這樣寫會抛出數組越界異常

List des1 = new  ArrayList( 3 );
Collections.copy(des1,src1);
           

ArrayList的capacity(容納能力大小)可以指定(最好指定),而初始化時size的大小永遠預設為0,隻有在進行add和remove等相關操作 時,size的大小才變化。

是以因為des1.size()為0;3表示的是這個List的容納能力為3,并不是說des1中就有了3個元素。然而進行copy()時候,首先做的是将desc1的size和src1的size大小進行比較,隻有當desc1的 size 大于或者等于src1的size時才進行拷貝,否則抛出IndexOutOfBoundsException異常。

實際情形:

仍然是淺複制。誤導此文作者的原因是String對象不可改變,如果仿照clone方法舉得例子,改成StringBuffer就可以發現實質上還是淺複制了。

二,深複制的方法

序列化

public static <T> List<T> deepCopy(List<T> src) throws IOException, ClassNotFoundException {
	ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
	ObjectOutputStream out = new ObjectOutputStream(byteOut);
	out.writeObject(src);

	ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
	ObjectInputStream in = new ObjectInputStream(byteIn);
	@SuppressWarnings("unchecked")
	List<T> dest = (List<T>) in.readObject();
	return dest;
}
           

參考文章:一些不靠譜的java.util.List深複制方法

三,淺複制和别名是不同的。

别名是對同一個對象的另一個引用變量。如ArrayList sameList=myList;

sameList和myList是同一個ArrayList對象的引用變量,對myList對象的更改也就是對sameList對象的更改。

上述淺複制方法,如

ArrayList<E> temp2=(ArrayList)temp1.clone();
           

則是temp2複制了temp1中的元素,這些元素是引用變量,被複制的元素和原本的元素指向同一記憶體。對temp1中元素引用的對象的修改會影響到temp2中元素引用的對象;若隻是将temp1中元素重新引用新的對象,指向新的記憶體,則temp2中元素仍然引用原本的對象、指向原本的記憶體。

繼續閱讀