深入理解Java的淺克隆與深克隆

蘋果大大個發表於2019-06-28

前言

克隆,即複製一個物件,該物件的屬性與被複制的物件一致,如果不使用Object類中的clone方法實現克隆,可以自己new出一個物件,並對相應的屬性進行資料,這樣也能實現克隆的目的。

但當物件屬性較多時,這樣的克隆方式會比較麻煩,所以Object類中實現了clone方法,用於克隆物件。

Java中的克隆分為淺克隆與深克隆

一、實現克隆的方式

1.物件的類需要實現Cloneable介面

2.重寫Object類中的clone()方法

3.根據重寫的clone()方法得到想要的克隆結果,例如淺克隆與深克隆。

二、淺克隆與深克隆的區別

淺克隆:複製物件時僅僅複製物件本身,包括基本屬性,但該物件的屬性引用其他物件時,該引用物件不會被複制,即拷貝出來的物件與被拷貝出來的物件中的屬性引用的物件是同一個。

深克隆:複製物件本身的同時,也複製物件包含的引用指向的物件,即修改被克隆物件的任何屬性都不會影響到克隆出來的物件。

 

例子如下:

class Person implements Cloneable{

    private int age;
    private String name;

    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

    @Override
    protected Person clone() throws CloneNotSupportedException {  
        return (Person)super.clone();   //呼叫父類的clone方法
    }
}

測試程式碼:

public class CloneTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person = new Person(22,"LiLei");
        Person newPerson = person.clone();
        person.setAge(21);
        person.setName("HanMeimei");
        System.out.println(person.toString());
        System.out.println(newPerson.toString());
    }
}

測試結果:

Person{age=21, name='HanMeimei'}
Person{age=22, name='LiLei'}

即在克隆出新的物件後,修改被克隆物件的基本屬性,並不會影響克隆出來的物件。但當被克隆的物件的屬性引用其他物件時,此時會有不同的結果。

例子如下

/**
 * 學生類
 */
class Student implements Cloneable{
    private String name;
    private Achievement achievement; //成績

    public Student(String name, Achievement achievement) {
        this.name = name;
        this.achievement = achievement;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAchievement(Achievement achievement) {
        this.achievement = achievement;
    }

    public Achievement getAchievement() {
        return achievement;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", achievement=" + achievement +
                '}';
    }

    @Override
    protected Student clone() throws CloneNotSupportedException {
       return (Student) super.clone(); 
    }
}
    
/**
 * 成績類
 */
class Achievement implements Cloneable{
    private float Chinese;
    private float math;
    private float English;

    public Achievement(float chinese, float math, float english) {
        Chinese = chinese;
        this.math = math;
        English = english;
    }

    public void setChinese(float chinese) {
        Chinese = chinese;
    }

    public void setMath(float math) {
        this.math = math;
    }

    public void setEnglish(float english) {
        English = english;
    }

    @Override
    public String toString() {
        return "Achievement{" +
                "Chinese=" + Chinese +
                ", math=" + math +
                ", English=" + English +
                '}';
    }

    @Override
    protected Achievement clone() throws CloneNotSupportedException {
        return (Achievement) super.clone();
    }
}

測試程式碼:

public class CloneTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        Achievement achievement = new Achievement(100,100,100);
        Student student = new Student("LiLei",achievement);
        // 克隆出一個物件
        Student newStudent = student.clone();

        // 修改原有物件的屬性
        student.setName("HanMeimei");
        student.getAchievement().setChinese(90);
        student.getAchievement().setEnglish(90);
        student.getAchievement().setMath(90);

        System.out.println(newStudent);
        System.out.println(student);

    }
}

測試結果:

Student{name='LiLei', achievement=Achievement{Chinese=90.0, math=90.0, English=90.0}}
Student{name='HanMeimei', achievement=Achievement{Chinese=90.0, math=90.0, English=90.0}}

以上現象表明,上述克隆方式為淺克隆,並不會克隆物件的屬性引用的物件,當修改被克隆物件的成績時,克隆出來的物件也會跟著改變,即兩個物件的屬性引用指向的是同一個物件。

但只要修改一下Student類中重寫的clone()方法,即可實現深克隆。

修改程式碼如下:

@Override
    protected Student clone() throws CloneNotSupportedException {
        Student student =  (Student) super.clone();
        Achievement achievement = student.getAchievement().clone();
        student.setAchievement(achievement);
        return student;
    }

 測試結果:

Student{name='LiLei', achievement=Achievement{Chinese=100.0, math=100.0, English=100.0}}
Student{name='HanMeimei', achievement=Achievement{Chinese=90.0, math=90.0, English=90.0}}

 即在Student類中的clone()方法中再克隆一次Achievement物件,並賦值給Student物件。

值得一提的是,上文所說的淺拷貝只會克隆基本資料屬性,而不會克隆引用其他物件的屬性,但String物件又不屬於基本屬性,這又是為什麼呢?

這是因為String物件是不可修改的物件,每次修改其實都是新建一個新的物件,而不是在原有的物件上修改,所以當修改String屬性時其實是新開闢一個空間儲存String物件,並把引用指向該記憶體,而克隆出來的物件的String屬性還是指向原有的記憶體地址,所以String物件在淺克隆中也表現得與基本屬性一樣。

 

相關文章