大家都知道在Java中所有的類都是缺省的繼承自Java語言包中的Object類的,查看它的源碼,你可以把你的JDK目錄下的src.zip復制到其他地方然后解壓,里面就是所有的源碼。發(fā)現(xiàn)里面有一個訪問限定符為protected的方法clone():
protected native Object clone() throws CloneNotSupportedException;
仔細一看,它還是一個native方法,大家都知道native方法是非Java語言實現(xiàn)的代碼,供Java程序調(diào)用的,因為Java程序是運行在JVM虛擬機上面的,要想訪問到比較底層的與操作系統(tǒng)相關(guān)的就沒辦法了,只能由靠近操作系統(tǒng)的語言來實現(xiàn)。
為什么要克???
大家先思考一個問題,為什么需要克隆對象?直接new一個對象不行嗎?答案是:克隆的對象可能包含一些已經(jīng)修改過的屬性,而new出來的對象的屬性都還是初始化時候的值,所以當需要一個新的對象來保存當前對象的“狀態(tài)”就靠clone方法了。那么我把這個對象的臨時屬性一個一個的賦值給我新new的對象不也行嘛?可以是可以,但是一來麻煩不說,二來,大家通過上面的源碼都發(fā)現(xiàn)了clone是一個native方法,就是快啊,在底層實現(xiàn)的。提個醒,我們常見的Object a=new Object();Object b;b=a;這種形式的代碼復制的是引用,即對象在內(nèi)存中的地址,a和b對象仍然指向了同一個對象。而通過clone方法賦值的對象跟原來的對象時同時獨立存在的。扯了這么多廢話,終于進入正題了ORZ。
如何實現(xiàn)克隆
簡單到你不敢相信。直接在你的類的后面聲明implements Cloneable。關(guān)于這個接口,它的源碼如下:
public interface Cloneable {
}
可以看到它是一個空的接口,它的作用就是做標記。如果沒有實現(xiàn)Cloneable接口就直接使用clone方法,程序會拋出CloneNotSupportedException異常。
然后是重寫clone方法,并修改成public訪問級別。舉個經(jīng)典的栗子:
class Outer implements Cloneable {
public int name;
public Inner inner;
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args){
Outer o_one = new Outer();
o_one.inner = new Inner("zhangsan");
try {
Object obj = o_one.clone();
Outer o_two = (Outer)obj;
System.out.println(o_one==o_two); //打印false
System.out.println(o_one.inner.name.equals(o_two.inner.name)); //打印true
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
上面的代碼實現(xiàn)的其實是淺克隆,淺克隆對于引用類型僅拷貝引用,沒有真正地讓兩個對象獨立開來互相之間沒有任何關(guān)系。由于是淺克隆,使得imp2修改了child的某個屬性后會是的imp1中child的屬性也跟著改變?;蛘弑容^兩個對象的地址:imp1.child==imp2.child,返回的結(jié)果是true。但是,如果一個對象只包含原始數(shù)據(jù)域或者不可變對象域(比如String類型),推薦使用淺克隆。
深克隆
類中的所有引用類型做一些修改,讓它也實現(xiàn)Cloneable接口。然后修改本類中的clone方法:
public class Inner implements Cloneable{
public String name;
public Child(String name) {
this.name = name;
}
@Override
public String toString() {
return "Inner的name值為:" + name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
修改本類的clone方法:
static class Outer implements Cloneable {
public int count;
public Inner inner;
@Override
public Object clone() throws CloneNotSupportedException {
Outer obj = (Outer)super.clone();
obj.inner = (Inner) inner.clone();
return obj;
}
}
畫重點了:
- 需要重寫clone方法,不僅僅只調(diào)用父類的方法,還需調(diào)用屬性的clone方法;
- 對象之間100%數(shù)據(jù)分離
- 如果是對象存在引用類型的屬性,建議使用深克隆
- 深克隆比淺克隆要更加耗時,效率更低
解決多層克隆問題
如果引用類型里面還包含很多引用類型,或者內(nèi)層引用類型的類里面又包含引用類型,使用clone方法就會很麻煩。這時我們可以用序列化的方式來實現(xiàn)對象的深克隆。
public class Outer implements Serializable{
private static final long serialVersionUID = 369285298572941L; //最好是顯式聲明ID
public Inner inner;
public Outer myclone() {
Outer outer = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
outer = (Outer) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return outer;
}
}
Inner也必須實現(xiàn)Serializable,否則無法序列化:
public class Inner implements Serializable{
private static final long serialVersionUID = 872390113109L; //最好是顯式聲明ID
public String name = "";
public Inner(String name) {
this.name = name;
}
@Override
public String toString() {
return "Inner的name值為:" + name;
}
}
這樣也能使兩個對象在內(nèi)存空間內(nèi)完全獨立存在,互不影響對方的值。
Tips
- 在克隆方法中,如果我們需要對可變對象的final域也進行拷貝,由于final的限制,所以實際上是無法編譯通過的。因此為了實現(xiàn)克隆,我們需要考慮舍去該可變對象域的final關(guān)鍵字。
- 如果你決定用線程安全的類實現(xiàn)Cloneable接口,需要保證它的clone方法做好同步工作。默認的Object.clone方法是沒有做同步的。