歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

談談 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;
        }
    }
}
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());
    }
}

輸出為:

姓名: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;
        }
    }
}
   
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;
        }
    }
}

輸出為:

姓名: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());
    }
}

輸出:

姓名:Li,年齡:10,老師:Wu
姓名:Li,年齡:10,老師:Wu
姓名:Li,年齡:10,老師:Wu

幾行序列化和反序列化代碼,簡單粗暴,適合絕大多數情況,再也不用為復雜的克隆邏輯而擔憂了。

Copyright © Linux教程網 All Rights Reserved