談談 Java的克隆
為什么要克隆對象
做開發很少用到克隆的。我能想得到的是用于調用方法時作為參數傳遞,為了保證方法調用前后對象的內部結構不被破壞,可以克隆一個對象作為參數傳遞。
使類具有克隆能力
有人可能注意到 Object 類中有一個 native 方法clone
protected native Object clone() throws CloneNotSupportedException;
訪問修飾符是 protected,缺省的情況下Object 及其子類對象無法在別的類中訪問 clone(), 等同于所有類缺省沒有克隆能力。
要具備克隆能力,必須實現 Cloneable 接口:
public interface Cloneable {}
奇怪的是,這個接口是空的。然而不用想那么多,這只是個標記而已,同為標記接口的還有 java.io.Serializable 等。
Cloneable 存在有兩個理由:
- 出于安全考慮,不想讓所有的類都具有克隆能力,要求若想克隆必須實現此接口;
- 某個引用向上轉型為基類后,你就不知道它是否能克隆,此時可以使用 instanceof 關鍵字檢查該引用是否指向一個可克隆的對象。
要具備克隆能力,必須重寫父類的 clone() 方法,同時將訪問修飾符改為 public,必須使用 super.clone() 進行(淺)克隆。
super.clone() 做了什么
Object 中的 clone() 識別你要復制的是哪一個對象,然后為此對象分配空間,并進行對象的復制,將原始對象的內容一一復制到新對象的存儲空間中。
需要注意的是這里的復制是淺層復制(淺層克隆 shadow clone),下面舉一個淺層復制的例子:
public class Student implements Cloneable{
private String name;
private int age;
private Teacher teacher;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
@Override
public String toString() {
String str = "姓名:" + getName() + ",年齡:" + getAge() + ",老師:" + ((getTeacher()==null)?"未知":getTeacher().getName());
return str;
}
public Object clone(){
try {
return super.clone();
} catch (Exception e) {
return null;
}
}
}
</code></pre>
public class Teacher {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class CloneTest1 {
public static void main(String[] args) {
Student s1 = new Student();
s1.setAge(10);
s1.setName("Li");
Teacher teacher = new Teacher();
teacher.setName("Wu");
s1.setTeacher(teacher);
System.out.println(s1.toString());
Student s2 = (Student) s1.clone();
System.out.println(s2.toString());
s1.setAge(20);
s1.setName("Hu");
teacher.setName("Yang");
System.out.println(s2.toString());
}
}
</code></pre>
輸出為:
姓名:Li,年齡:10,老師:Wu
姓名:Li,年齡:10,老師:Wu
姓名:Li,年齡:10,老師:Yang
s1.setAge(20) 和 s1.setName("Hu") 都沒有影響到克隆對象 s2。為什么? 這里說說我的理解
基本數據類型或裝箱基本數據類型在方法中作為參數傳遞的時候,都是傳遞的值得拷貝,所以單從它來講已經做到了深層克隆。
String 類型你可以理解為是不可變的,一旦你做了改變(比如使用連接符做拼接),它也就變成另外一個對象了,不會影響到原對象,所以單從它來講也做到了深層克隆。
teacher.setName("Yang") 影響到了克隆對象 s2,所以整個學生對象的克隆是淺層克隆。想要實現深層克隆,做以下修改
public class Teacher implements Cloneable{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Object clone(){
try {
return super.clone();
} catch (Exception e) {
return null;
}
}
}
</code></pre>
public class Student implements Cloneable{
private String name;
private int age;
private Teacher teacher;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
@Override
public String toString() {
String str = "姓名:" + getName() + ",年齡:" + getAge() + ",老師:" + ((getTeacher()==null)?"未知":getTeacher().getName());
return str;
}
public Object clone(){
try {
Student stu = (Student) super.clone();
stu.setTeacher((Teacher)stu.getTeacher().clone());
return stu;
} catch (Exception e) {
return null;
}
}
}
</code></pre>
輸出為:
姓名:Li,年齡:10,老師:Wu
姓名:Li,年齡:10,老師:Wu
姓名:Li,年齡:10,老師:Wu
通過序列化進行深層拷貝
按照上面的深層克隆方法,如果類的結構不同,clone() 代碼邏輯就不同,而且還可能涉及到大量的遍歷和判斷等復雜的操作。
嫌麻煩? 試試用序列化做深層拷貝吧。將對象進行序列化后再進行反序列化,其效果相當于克隆對象。
下面改改代碼來證明這句話:
public class Student implements Serializable{
private String name;
private int age;
private Teacher teacher;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
@Override
public String toString() {
String str = "姓名:" + getName() + ",年齡:" + getAge() + ",老師:" + ((getTeacher()==null)?"未知":getTeacher().getName());
return str;
}
}
public class Teacher implements Serializable{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class CloneTest1 {
public static void main(String[] args) throws Exception{
Student s1 = new Student();
s1.setAge(10);
s1.setName("Li");
Teacher teacher = new Teacher();
teacher.setName("Wu");
s1.setTeacher(teacher);
System.out.println(s1.toString());
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objOutputStream.writeObject(s1);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objInputStream = new ObjectInputStream(byteArrayInputStream);
Student s2 = (Student) objInputStream.readObject();
System.out.println(s2.toString());
s1.setAge(20);
s1.setName("Hu");
teacher.setName("Yang");
System.out.println(s2.toString());
}
}</code></pre>
輸出:
姓名:Li,年齡:10,老師:Wu
姓名:Li,年齡:10,老師:Wu
姓名:Li,年齡:10,老師:Wu
幾行序列化和反序列化代碼,簡單粗暴,適合絕大多數情況,再也不用為復雜的克隆邏輯而擔憂了。
來自:http://www.cnblogs.com/xmsx/p/5852473.html