當一個物件需要被clone時,該物件的類別需要實作Cloneable ,但是Cloneable並沒有定義clone()這個method,光靠實作Cloneable,很難認定這個物件可以被clone,因為類別有可能沒有撰寫clone()這個method。
由於所有類別都會繼承Object這個類別,如果類別沒有撰寫clone(),只能依靠reflection的機制,在clone()被呼叫的時候,呼叫Object.clone(),Object.clone()會先檢查類別有沒有實作Cloneable ,沒有的話會丟出CloneNotSupportedException。有實作的話,會先生成一個新的物件,並把物件中每一個欄位的值複製到新的物件,然後回傳。下面的範例,p2是從p1clone出來的,所以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()有什麼好處,該注意什麼,我們明天再聊聊吧~~