談談 Java的克隆

ug2847 8年前發布 | 5K 次閱讀 Java Java開發

為什么要克隆對象

做開發很少用到克隆的。我能想得到的是用于調用方法時作為參數傳遞,為了保證方法調用前后對象的內部結構不被破壞,可以克隆一個對象作為參數傳遞。

使類具有克隆能力

有人可能注意到 Object 類中有一個 native 方法clone

protected native Object clone() throws CloneNotSupportedException;

訪問修飾符是 protected,缺省的情況下Object 及其子類對象無法在別的類中訪問 clone(), 等同于所有類缺省沒有克隆能力。

要具備克隆能力,必須實現 Cloneable 接口

public interface Cloneable {}

奇怪的是,這個接口是空的。然而不用想那么多,這只是個標記而已,同為標記接口的還有 java.io.Serializable 等。

Cloneable 存在有兩個理由:

  1. 出于安全考慮,不想讓所有的類都具有克隆能力,要求若想克隆必須實現此接口;
  2. 某個引用向上轉型為基類后,你就不知道它是否能克隆,此時可以使用 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

 

 本文由用戶 ug2847 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!