天天看點

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

前言

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

但當對象屬性較多時,這樣的克隆方式會比較麻煩,是以Object類中實作了clone方法,用于克隆對象。

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

一、實作克隆的方式

1.對象的類需要實作Cloneable接口

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

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

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

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

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

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

例子如下:

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對象在淺克隆中也表現得與基本屬性一樣。