前言
在我們編碼過程中,我們有時候會遇見這種情況:有一個對象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問題。