J2SE(十八)Object之Clone

前言

        在我們編碼過程中,我們有時候會遇見這種情況:有一個對象A,在某一時刻A中已經包含了一些有效值,此時可能會需要一個和A完全相同的新對象B,並且此後對B的任何改動都不能影響到A中的值,也就是說,A與B是兩個獨立的對象,但B的初始值是由A對象確定的。在Java語言中實現它的方法有很多,今天我們就來探討一種簡單而高效的實現方法,即通過Object的clone方法實現。

一、相關類

Java中跟克隆有關的兩個類分別是Cloneable接口和Object類中的clone方法,通過兩者的協作來實現克隆。Clonable接口api描述如下:

java.lang.Cloneable 接口(以下源引JavaTM 2 Platform Standard Ed. 5.0 API DOC)
此類實現了 Cloneable 接口,以指示 Object.clone() 方法可以合法地對該類實例進行按字段複製。 如果在沒有實現 Cloneable 接口的實例上調用 Object 的 clone 方法,則會導致拋出 CloneNotSupportedException異常。 
按照慣例,實現此接口的類應該使用公共方法重寫 Object.clone(它是受保護的)。請參閱 Object.clone(),以獲得有關重寫此方法的詳細信息。 
注意,此接口不包含 clone 方法。因此,因爲某個對象實現了此接口就克隆它是不可能的。即使 clone 方法是反射性調用的,也無法保證它將獲得成功。
       上面的意思是Cloneable接口沒有任何方法,僅是個標誌接口(tagging interface),若要具有克隆能力,實現Cloneable接口的類必須重寫從Object繼承來的clone方法,並調用Object的clone方法。
二、例子

public class Person implements Cloneable{
	
	private int id;
	
	private String name;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Person(int id, String name) {
		super();
		this.id = id;
		this.name = name;
	}

	@Override
	public String toString() {
		return "Person [id=" + id + ", name=" + name + "]";
	}

	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
}
測試方法

public static void main(String[] args) throws CloneNotSupportedException {
		Person p=new Person(1, "zhangsan");
		Person p1=(Person) p.clone();
		p1.setName("wangwu");
		System.out.println(p);
		System.out.println(p1);
		System.out.println(p==p1);
		System.out.println(p.equals(p1));
	}
結果集

Person [id=1, name=zhangsan]
Person [id=1, name=wangwu]
false
false
從結果集可以看出,通過Person p的clone方法得到了Person p1,p與p1不相等且對p1做的修改不影響p。
三、淺克隆與深度克隆

        從上面的程序我們可以看出,clone調用了object的clone方法,創建一個對象然後一一賦值。對於原始類型跟不可變類型(String,Long...)來說是這種方法是沒有問題的,但是如果是複雜類型比如list的話,那麼clone對象跟原始對象會指向同一個對象。
我們將上面的代碼做一定的修改,在Person對象中添加屬性List<String> list

private List<String> list;
	
	public List<String> getList() {
		return list;
	}

	public void setList(List<String> list) {
		this.list = list;
	}

	@Override
	public String toString() {
		return "Person [id=" + id + ", name=" + name + ", list=" + list + "]";
	}

	public Person(int id, String name, List<String> list) {
		super();
		this.id = id;
		this.name = name;
		this.list = list;
	}
測試代碼修改爲

public static void main(String[] args) throws CloneNotSupportedException {
		List<String> list=new ArrayList<String>();
		list.add("a");
		list.add("b");
		list.add("c");
		Person p=new Person(1, "zhangsan",list);
		Person p1=(Person) p.clone();
		p1.getList().add("d");
		System.out.println(p);
		System.out.println(p1);
		System.out.println(p==p1);
		System.out.println(p.equals(p1));
	}
結果集

Person [id=1, name=zhangsan, list=[a, b, c, d]]
Person [id=1, name=zhangsan, list=[a, b, c, d]]
false
false
        可以看出,p跟p1的List指向同一個應用,對p1的list做了修改影響到p的list值,這種情況稱之爲淺克隆,即被複制的對象的所有成員屬性都有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象。換言之,淺層複製僅僅複製所考慮的對象,而不復制它所引用的對象。那什麼是深克隆呢?深克隆指的是被複制對象的所有變量都含有與原來的對象相同的值,除去那些引用其他對象的變量。那些引用其他對象的變量將指向被複制過的新對象,而不是原有的那些被引用的對象。換言之,深層複製要複製的對象引用的對象都複製一遍。

那麼如何實現深克隆呢,常用的方法是採用串行化,重寫Person的clone方法,代碼如下

	@Override
	public Object clone(){
		 ObjectInputStream oi=null;
		 Object o=null;
		try {
			//將對象寫到流裏
			 ByteArrayOutputStream bo=new ByteArrayOutputStream();
			 ObjectOutputStream oo=new ObjectOutputStream(bo);
			 oo.writeObject(this);
			 //從流裏讀出來
			 ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
			 oi=new ObjectInputStream(bi);
			 o=oi.readObject();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return o;
	}
這樣就避免了引用類型的clone問題。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章