在java中,如果需要有拷貝問題,都會使用到父類Object
的Clone
方法,能夠為我們提供對象的拷貝方法,在使用過程中這里會有很多各式各樣的問題。這里簡單羅列一下個人所遇到的一些問題:
1. 實現 Cloneable接口以及為何使用super.clone()
Object的clone方法,就是拷貝一份副本出來,要求副本狀態或者屬性與原型要一致,但是相互要獨立,改變原型或者副本,兩者不會相互干擾。
Object a = new Object();
Object b = a;
//雖然a和b相同,但是二者是指向同一個對象,所以不是克隆出來的
Object
提供了clone
方法,但是需要子類實現Cloneable
接口,換句話說就是,只有實現Cloneable
接口的類能被拷貝。在沒有實現
Cloneable
接口的實例上調用Object
的clone
方法,則會導致拋出CloneNotSupportedException
異常。
在子類覆蓋了clone方法后,調用super.clone()方法,Object的clone()
是一個native方法,會返回一個拷貝的實例。
@Override
public Person clone(){
try{
Person copy = (Person) super.clone();//返回一個拷貝的實例
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
}
值得注意的是,super.clone()只是進行淺拷貝,這一點會在下面進行討論。
使用super.clone()
的好處是會檢查是否實現了Cloneable
,否則會拋異常,換句話說就是 ** 防止不可復制的類調用clone()方法**
另外一種方法是提供拷貝構造器實現對象拷貝,好處就是構造器可以傳遞參數,而clone()
卻不可以。
2. 深拷貝和淺拷貝問題
淺拷貝是指拷貝對象時僅僅拷貝對象本身(包括對象中的基本變量),而不拷貝對象包含的引用指向的對象。深拷貝不僅拷貝對象本身,而且拷貝對象包含的引用指向的所有對象
淺拷貝、深拷貝圖:
** super.clone()默認是只進行淺拷貝的**
class Person implements Cloneable{
String name;
Person parent;
public Person( String name){
this.name = name;
this.parent = null;
}
}
Person father = new Person("father");
Person Jason = new Person("jason");
Jason.parent = father;
Person Bom = Jason.clone();//事實上,未覆蓋的clone()方法調用的是Object的clone(),是淺復制
//測試代碼
Bom.name = "Bom";
Bom.parent.name = "mother";
print(Bom.name); // "Bom"
print(Bom.parent.name); // "mother"
pirnt(Jason.name); // " Jason"
pirnt(Jason.parent.name); // "mother" ,而不是"father"
print(Bom == Jason); // "false",clone會生成一個新的對象
print(Bom.parent == Jason.parent); // "true", 指向相同的對象
除了不可變對象或者基本數據類型,java的對象復制只是對引用的復制,所以拷貝的副本和原型引用了同一個對象,因此根據業務需求,有時候我們需要深度拷貝:
** 在clone()中,可變的對象都需要進行重寫clone(),保證不會引用到同一個對象,否則會造成各種問題。**
class Person implements Cloneable{
String name;
Person parent;
public Person( String name){
this.name = name;
this.parent = null;
}
public Person clone(){
Person copy = null;
try{
copy = (Person) super.clone();
if(this.parent != null)
copy.parent = this.parent.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return copy;
}
}
3. 在Iterator中注意的問題
在Effective Java書中寫道,clone就是另外一種構造器,你必須保證他不能傷害到原始的對象,并確保正確地創建被克隆。
在java 容器中,拷貝的方法有:
直接通過構造器初始化,但是只是淺拷貝,拷貝的只是對象的引用
List cloneList=new ArrayList(src);
Collections.copy(List desc,List src) 方法,注意desc和src的size,不然會出現IndexOutOfBoundsException異常,這個方法同樣是淺拷貝
通過迭代器循環逐個進行深度的clone()
Clone方法就像構造方法,所以在里面最好只調用復制對象的final方法或者private方法,否則子類有可能重寫該方法,這樣子類在調用clone時會優先調用重寫的方法,可能復制出來的對象不一致了。
總結
最后的原則:
首先調用super.clone(),然后修正需要改變的域。
要么寫一個良好的clone()方法,否則就改用使用其他方法進行拷貝,因為clone會出現特別多意想之外的問題(大部分是深度和淺度復制問題),另外一種方法是直接不使用clone方法,而是采用拷貝構造器直接生成,自己修正需要改變的域,或者通過序列化的方法進行拷貝。
在clone方法中,一般通過super.clone()獲取拷貝實例,再修改域,因此不能含有final變量,這是clone的另一個不足。