結合實例詳解clone()函數,Cloneable接口以及深拷貝與淺拷貝的問題

package job;
/**
 * ps:java中的clone()方法的功用類似於C++中的拷貝構造函數
 * 拷貝對象還可以用對象流中轉一下獲得,需要實現標記型接口Serializable
 * Serializable接口中的方法對程序是不可見的,因此實現了該接口的類不需要實現
 * 額外的方法,當把一個序列化的對象寫入ObjuctOutputStream時,JVM就會實現
 * Serializable接口中的方法,將一定格式的文本-對象的序列化信息,寫入OBjuceOutputStream
 * 輸出流的目的地
 * 使用對象流把一個對象寫入文件時不僅需要保證該對象是序列化的,而且該對象的
 * 成員對象也必須是序列化的
*/
import java.io.Serializable;
import java.util.Date;

/**
 * Cloneable是標記型接口(另一個常見的就是Serializable),聲明瞭該接口表明可用clone()方法
 * 如果不聲明Cloneable接口使用clone()方法就會報CloneNotSupportException
 * 按慣例要重寫protected clone()方法爲public的,但是clone()是Object的protected方法
 * 已有一個field to field的淺拷貝實現,因此不像其它接口的抽象方法那樣必須重寫
 * 但是因爲Object中的clone()方法的訪問權限是protected的,這就意味着,如果一個對象想使用
 * 該方法得到自己的一個複製品,就必須保證自己的類與Object類在同一個包中,這顯然是不可能的,
 * 因爲Java不允許用戶編寫的類擁有java.lang這樣的包名(儘管可以編譯擁有java.lang包名的類,
 * 但運行時JVM拒絕加載這樣的類),爲了能讓一個對象使用clone()方法,創建該對象的類需要重寫(覆蓋)
 * clone()方法,並且講訪問權限提升爲public權限,爲了能使用被覆蓋的clone()方法,只需在重寫的clone()
 * 方法中使用關鍵字super調用Object類的clone()方法即可。
 * 再者對象中有非基本類型的域且其內容有可能會發生改變時,必須重寫clone()方法爲深拷貝
 * @author shijin
 *
 */
public class TestClone implements Cloneable{

	private Date date;
	private DeepCopy d;
	
	public Date getT() {
		return date;
	}
	
	public DeepCopy getD() {
		return d;
	}

	public void setT(Date date) {	
		this.date = date;
	}
	
	public void setD(DeepCopy d) {
		this.d = d;
	}

	@Override
	public boolean equals(Object obj) {
		if(obj == null)
			return false;
		else {
			if(obj instanceof TestClone) {
				TestClone t = (TestClone)obj;
				if(this.date.equals(t.date) && this.d.equals(t.d))
					return true;
			}
			return false;
		}
		
	}

	/**
	 * 其實clone()的默認實現類似於函數函數中對date域的處理:field to field,淺拷貝,shallow copy
	 * 對於域中存在的非初始類型且其內容有可能發生變化的情況
	 * 就要改寫clone()方法實現深拷貝deep copy,如函數中對d域的處理
	 * 不然拷貝對象與原對象不獨立,兩個對象的非初始類型域會共享內存空間
	 * 一個對象內存內容的改變會造成兩個對象同步改變
	 * 
	 * 這裏改變對象的內容與改變對象的引用不是一個概念
	 * field to field類型的複製只會造成兩個相等的對象
	 * 對兩個相等的對象,即爲兩個對象引用指向同一塊內存空間
	 * 一個對象引用指向的改變不會影響另一個對象
	 * 只不過改變指向的對象指向了另一塊內存,原指向斷開被新的指向替代
	 * 未改變的對象依然指向原內存,兩個對象不再相等
	 * 但是對象內容的改變會影響另一個對象,因爲修改的是同一塊內存
	 * 對此處的理解可以參考String變量的不可變性
	 * String類型的變量作爲變量可以相繼指向不同字符串常量
	 * 但對於一個固定的指向,其內存空間裏的內容是不能改變的
	 * String s = new String("a");s ="a" + "b";兩句話的內存分析
	 * s變量在棧內存
	 * new出來的String對象在堆內存
	 * "a"、"b"、"ab"三個字符串常量在數據段(常量池)
	 * s定義時指向堆內存中的String對象"a"
	 * 第二句執行後指向數據段中的字符串常量"ab"
	 * 內存中的內容不變,只是字符串變量的指向變了
	 * 			
	*/
	@Override
	public Object clone() throws CloneNotSupportedException {
//		TestClone t = (TestClone)super.clone();
//		t.setT(this.getT());
//		t.setD(this.getD());//淺拷貝
//		t.setD(new DeepCopy(this.d.getX()));//深拷貝
//		或者如下,如果DeepCopy實現了Cloneable接口的話,這裏就有點遞歸的意思了
//		t.setD((DeepCopy)this.d.clone());
//		return t;
		
		return super.clone(); 

//		return new TestClone();
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		TestClone t = new TestClone();
		t.setT(new Date());
		t.setD(new DeepCopy(1));
		TestClone tClone = null;
		try {
			tClone = (TestClone)t.clone();
		} catch (CloneNotSupportedException e1) {
			e1.printStackTrace();
		}
		
//		這個!=是必須的,定義要求拷貝對象與原對象獨立,不然就失去拷貝的意義了
		System.out.println("t " + ((t != tClone)?"!=":"==") + " t.clone()");
//		clone()中對d進行深拷貝時t.d != t.clone().d
		System.out.println("t.d " + ((t.d != tClone.d)?"!=":"==") + " t.clone().d");
//		clone()中對t進行淺拷貝t.date == t.clone().date
		System.out.println("t.date " + ((t.date != tClone.date)?"!=":"==") + " t.clone().date");
		
//		這個==雖然不是必須的,但是隻要類及其父類clone()方法中的拷貝對象都是通過super.clone()獲得
//		t.getClass() == tClone.getClass()就成立
		System.out.println("t.getClass() " + ((t.getClass() == tClone.getClass())?"==":"!=") + " tClone.getClass()");
		System.out.println("\tt = " + t);
		System.out.println("\ttClone = " + tClone);
//		即使clone()採用默認實現,仍然返回true,自動識別類型
//		Object中的clone執行的時候使用了RTTI(run-time type identification)的機制,即多態的實現機制
//		動態得找到目前正在調用clone方法的那個reference,根據它的大小申請內存空間
//		然後進行bitwise的複製,將該對象的內存空間完全複製到新的空間中去,從而達到shallow copy的目的。 
//		所以調用super.clone() 得到的是當前調用類的副本,而不是父類的副本。
		System.out.println("\ttClone 是不是TestClone的實例:" + (tClone instanceof TestClone));
		System.out.println("\tt.getClass() = " + t.getClass());
		System.out.println("\ttClone.getClass() = " + tClone.getClass());
		
//		這個==也不是必須的,比如在clone()和equals()都改寫時
//		若對象中有一個域爲unique,則該對象的克隆就可能與該對象不相等
		System.out.println("t " + ((t.equals(tClone))?"equales":"doesn't eaual") + " t.clone");
				
//		本段代碼驗證淺拷貝與深拷貝的區別		
		System.out.println("t.d.x改動前:" + t.d);
		System.out.println("tClone.d.x改動前:" + tClone.d);
		
		t.d.setX(2);
		
		System.out.println("t.d.x改動後:" + t.d);
		System.out.println("tClone.d.x改動前:" + tClone.d);

//		本段代碼參考上面驗證改變引用與改變內容的區別
//		對date的實驗中直接更換date域的引用,而不是改變date的內容,所以體現不出淺拷貝的弊端
//		後期date域指向外部引用後兩個對象也獨立了
		System.out.println(t.date);
		System.out.println(tClone.date);
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		tClone.setT(new Date());
		
		System.out.println(t.date);
		System.out.println(tClone.date);	
	}
}
/**
 * 驗證深拷貝的輔助類
 * @author shijin
 *
 */
class DeepCopy implements Cloneable{
	int x;
	
	public DeepCopy(int x) {
		this.x = x;
	}

	public int getX() {
		return x;
	}

	public void setX(int x) {
		this.x = x;
	}

	@Override
	public String toString() {
		return x+"";
	}
	
	@Override
	protected Object clone() throws CloneNotSupportedException {
		//成員爲基本類型,不用改寫方法
		return super.clone();
	}

	@Override
	public boolean equals(Object obj) {
		if(obj != null && obj instanceof DeepCopy) {
			DeepCopy d = (DeepCopy)obj;
			if(d.x == this.x)
				return true;
		}
		return false;
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章