當一個物件需要被clone時,該物件的類別需要實作Cloneable
,但是Cloneable
並沒有定義clone()
這個method,光靠實作Cloneable
,很難認定這個物件可以被clone,因為類別有可能沒有撰寫clone()
這個method。
由於所有類別都會繼承Object
這個類別,如果類別沒有撰寫clone()
,只能依靠reflection的機制,在clone()
被呼叫的時候,呼叫Object.clone()
,Object.clone()
會先檢查類別有沒有實作Cloneable ,沒有的話會丟出CloneNotSupportedException
。有實作的話,會先生成一個新的物件,並把物件中每一個欄位的值複製到新的物件,然後回傳。下面的範例,p2
是從p1
clone出來的,所以p2
每個欄位的值會跟p1
一模一樣。
class PersonForClone implements Cloneable {
private String name;
private int age;
public PersonForClone(String name, int age) {
this.name = name;
this.age = age;
}
@Override public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public static void main(String[] args) {
PersonForClone p1 = new PersonForClone("Lucky", 10);
try {
PersonForClone p2 = (PersonForClone) p1.clone();
System.out.print("Person: " + p2); //Person{name='Lucky', age=10'}
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
Object.clone()
除了檢查類別有沒有實作Cloneable
,還會遵守下面幾個規則去clone物件:
x.clone() != x
:clone出來的物件會是一個新的物件,所以不會跟原本的reference相同x.clone().getClass() == x.getClass()
:clone出來的物件和原物件的類別一定是相同x.clone().equals(x)
:clone出來的物件因為跟原物件一模一樣,所以用equals()
檢查會是相等的但這些規則對於能否正確clone物件,其實還遠遠不夠,舉例來說,物件中的欄位是array或list,如果單純地用Object.clone()
,clone之後雖然新舊兩個物件的reference不同,但物件中的array或list的reference還是相同。
下面使用System.identityHashCode
驗證p1
和p2
的jobs後發現,這兩個list確實為同一個list。這在後面會造成很多問題,例如p1
的jobs修改了,要使用p2
的jobs時,才發現被修改了,甚至p1
的jobs被設為null
回收掉,p2
的jobs
因為指到同一個reference,也跟著消失,如果p2
的jobs
後面還有使用,會出現runtime error。
class PersonForClone implements Cloneable {
private String name;
private int age;
private String[] jobs;
public PersonForClone(String name, int age , String[] jobs) {
this.name = name;
this.age = age;
this.jobs = jobs;
}
@Override public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age + '\'' +
", jobs=" + jobs +
'}';
}
public static void main(String[] args) {
String[] jobs = {"Developer", "Designer", "PM"};
System.out.println("p1 jobs reference: " + Integer.toHexString(System.identityHashCode(jobs)));
PersonForClone p1 = new PersonForClone("Lucky", 10, jobs);
try {
PersonForClone p2 = (PersonForClone) p1.clone();
System.out.println("p2: " + p2);
System.out.println("p2 jobs reference: " + Integer.toHexString(System.identityHashCode(p2.jobs)));
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
所以這時候就可以發現,自己覆寫clone()處理一些細節是有必要的,至於覆寫clone()有什麼好處,該注意什麼,我們明天再聊聊吧~~