Java深克隆和淺克隆的原理及實(shí)現(xiàn)

Java深克隆和淺克隆的原理及實(shí)現(xiàn)

參考:
http://www.reibang.com/p/94dbef2de298
https://www.cnblogs.com/shakinghead/p/7651502.html

Java 中的數(shù)據(jù)類型分為基本數(shù)據(jù)類型和引用數(shù)據(jù)類型。對于這兩種數(shù)據(jù)類型,在進(jìn)行賦值操作洋措、用作方法參數(shù)或返回值時(shí),會有值傳遞和引用(地址)傳遞的差別。

根據(jù)對對象屬性的拷貝程度(基本數(shù)據(jù)類和引用類型)率挣,會分為兩種:
淺拷貝 (Shallow Copy)
深拷貝 (Deep Copy)

淺拷貝

淺拷貝是按位拷貝對象清寇,它會創(chuàng)建一個(gè)新對象,這個(gè)對象有著原始對象屬性值的一份精確拷貝做葵。如果屬性是基本類型占哟,拷貝的就是基本類型的值;如果屬性是內(nèi)存地址(引用類型)酿矢,拷貝的就是內(nèi)存地址 榨乎,因此如果其中一個(gè)對象改變了這個(gè)地址,就會影響到另一個(gè)對象瘫筐。即默認(rèn)拷貝構(gòu)造函數(shù)只是對對象進(jìn)行淺拷貝復(fù)制(逐個(gè)成員依次拷貝)蜜暑,即只復(fù)制對象空間而不復(fù)制資源。

(1) 對于基本數(shù)據(jù)類型的成員對象策肝,因?yàn)榛A(chǔ)數(shù)據(jù)類型是值傳遞的肛捍,所以是直接將屬性值賦值給新的對象≈冢基礎(chǔ)類型的拷貝拙毫,其中一個(gè)對象修改該值,不會影響另外一個(gè)棺禾。
(2) 對于引用類型恬偷,比如數(shù)組或者類對象,因?yàn)橐妙愋褪且脗鬟f帘睦,所以淺拷貝只是把內(nèi)存地址賦值給了成員變量袍患,它們指向了同一內(nèi)存空間。改變其中一個(gè)竣付,會對另外一個(gè)也產(chǎn)生影響诡延。
結(jié)構(gòu)圖如下:


1.png
淺拷貝的實(shí)現(xiàn)

實(shí)現(xiàn)對象拷貝的類,需要實(shí)現(xiàn) Cloneable 接口古胆,并覆寫 clone() 方法

示例:

package com.lvyuanj.core.model;

import lombok.Data;

@Data
public class Teacher implements Cloneable {

    private String name;

    private int age;

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

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
package com.lvyuanj.core.model;

import lombok.Data;

@Data
public class Student implements Cloneable{

    private String name;

    private int age;

    private Teacher teacher;

    public Student(String name,int age,Teacher teacher){
        this.name = name;
        this.age = age;
        this.teacher = teacher;
    }

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

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

    public static void main(String[] args) throws CloneNotSupportedException {

        Teacher teacher = new Teacher("王老師",30);

        Student student = new Student("張三", 20, teacher);

        System.out.println("student:"+student + ",hashcode"+System.identityHashCode(student));

        System.out.println("teacher:"+teacher+ ",hashcode"+System.identityHashCode(teacher));

        Student student1 = (Student) student.clone();
        student1.setName("李四");
        student1.setAge(40);
        Teacher teacher1 = student1.getTeacher();
        teacher1.setAge(90);

        System.out.println("student1:"+student1 + ",hashcode:"+System.identityHashCode(student1));
        System.out.println("teacher1 "+teacher1 + ",hashcode:"+System.identityHashCode(student1.getTeacher()));

        System.out.println("student:"+student + ",hashcode"+System.identityHashCode(student));

    }
}

運(yùn)行結(jié)果:

student:Student{name='張三', age=20, teacher=Teacher{name='王老師', age=30}},hashcode1872034366
teacher:Teacher{name='王老師', age=30},hashcode1581781576
student1:Student{name='李四', age=40, teacher=Teacher{name='王老師', age=90}},hashcode:1725154839
teacher1 Teacher{name='王老師', age=90},hashcode:1581781576
student:Student{name='張三', age=20, teacher=Teacher{name='王老師', age=90}},hashcode1872034366

由輸出的結(jié)果可見肆良,通過 student.clone() 拷貝對象后得到的 student1筛璧,和 student 是兩個(gè)不同的對象。student 和 student1 的基礎(chǔ)數(shù)據(jù)類型的修改互不影響惹恃,而引用類型 Teacher 修改后是會有影響的夭谤。
student的基礎(chǔ)數(shù)據(jù)類型:name = "張三" ,age = 20
student1的基礎(chǔ)數(shù)據(jù)類型:name = "李四" 巫糙,age = 90

student1的引用類型teacher朗儒,修改age = 90 之后,student 中teacher.age =90 ;
可以通過hascode打印的值看的出來参淹,引用類型的是相同內(nèi)存地址醉锄,所以修改copy后中teacher.age,直接影響原來student中teacher對象的age

深拷貝

通過上面的例子可以看到浙值,淺拷貝會帶來數(shù)據(jù)安全方面的隱患恳不,例如我們只是想修改了 student 的 teacher,但是 student1 的 teacher 也被修改了开呐,因?yàn)樗鼈兌际侵赶虻耐粋€(gè)地址烟勋。所以,此種情況下筐付,我們需要用到深拷貝卵惦。

深拷貝,在拷貝引用類型成員變量時(shí)家妆,為引用類型的數(shù)據(jù)成員另辟了一個(gè)獨(dú)立的內(nèi)存空間,實(shí)現(xiàn)真正內(nèi)容上的拷貝冕茅。

  1. 深拷貝特點(diǎn)
    (1) 對于基本數(shù)據(jù)類型的成員對象伤极,因?yàn)榛A(chǔ)數(shù)據(jù)類型是值傳遞的,所以是直接將屬性值賦值給新的對象姨伤∩谄海基礎(chǔ)類型的拷貝,其中一個(gè)對象修改該值乍楚,不會影響另外一個(gè)(和淺拷貝一樣)当编。
    (2) 對于引用類型,比如數(shù)組或者類對象徒溪,深拷貝會新建一個(gè)對象空間忿偷,然后拷貝里面的內(nèi)容,所以它們指向了不同的內(nèi)存空間臊泌。改變其中一個(gè)鲤桥,不會對另外一個(gè)也產(chǎn)生影響。
    (3) 對于有多層對象的渠概,每個(gè)對象都需要實(shí)現(xiàn) Cloneable 并重寫 clone() 方法茶凳,進(jìn)而實(shí)現(xiàn)了對象的串行層層拷貝嫂拴。
    (4) 深拷貝相比于淺拷貝速度較慢并且花銷較大。

3.深拷貝的實(shí)現(xiàn)方法主要有兩種:
(1)贮喧、通過重寫clone方法來實(shí)現(xiàn)深拷貝
(2)筒狠、通過對象序列化實(shí)現(xiàn)深拷貝

結(jié)構(gòu)圖如下:


2.png

一、通過重寫clone方法來實(shí)現(xiàn)深拷貝
示例:

package com.lvyuanj.core.model;

import lombok.Data;

@Data
public class Teacher implements Cloneable {

    private String name;

    private int age;

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

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
package com.lvyuanj.core.model;

import lombok.Data;

@Data
public class Student implements Cloneable{

    private String name;

    private int age;

    private Teacher teacher;

    public Student(String name,int age,Teacher teacher){
        this.name = name;
        this.age = age;
        this.teacher = teacher;
    }

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Student student = (Student) super.clone();
        student.teacher = (Teacher) teacher.clone();
        return student;
    }

    public static void main(String[] args) throws CloneNotSupportedException {

        Teacher teacher = new Teacher("王老師",30);

        Student student = new Student("張三", 20, teacher);

        System.out.println("student:"+student + ",hashcode"+System.identityHashCode(student));

        System.out.println("teacher:"+teacher+ ",hashcode"+System.identityHashCode(teacher));

        Student student1 = (Student) student.clone();
        student1.setName("李四");
        student1.setAge(40);
        Teacher teacher1 = student1.getTeacher();
        teacher1.setAge(90);

        System.out.println("student1:"+student1 + ",hashcode:"+System.identityHashCode(student1));
        System.out.println("teacher1 "+teacher1 + ",hashcode:"+System.identityHashCode(student1.getTeacher()));

        System.out.println("student:"+student + ",hashcode"+System.identityHashCode(student));

    }
}

運(yùn)行結(jié)果:

student:Student{name='張三', age=20, teacher=Teacher{name='王老師', age=30}},hashcode1872034366
teacher:Teacher{name='王老師', age=30},hashcode1581781576
student1:Student{name='李四', age=40, teacher=Teacher{name='王老師', age=90}},hashcode:1725154839
teacher1 Teacher{name='王老師', age=90},hashcode:1670675563
student:Student{name='張三', age=20, teacher=Teacher{name='王老師', age=30}},hashcode1872034366

二箱沦、通過對象序列化實(shí)現(xiàn)深拷貝
雖然層次調(diào)用clone方法可以實(shí)現(xiàn)深拷貝辩恼,但是顯然代碼量實(shí)在太大。特別對于屬性數(shù)量比較多饱普、層次比較深的類而言运挫,每個(gè)類都要重寫clone方法太過繁瑣。將對象序列化為字節(jié)序列后套耕,默認(rèn)會將該對象的整個(gè)對象圖進(jìn)行序列化谁帕,再通過反序列即可完美地實(shí)現(xiàn)深拷貝。

示例:

package com.lvyuanj.core.model;

import lombok.Data;

import java.io.Serializable;

@Data
public class TeacherA implements Serializable {

    private String name;
    private int sex;

    public TeacherA(String name,int sex){
        this.name = name;
        this.sex = sex;
    }

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

package com.lvyuanj.core.model;

import lombok.Data;

import java.io.*;

@Data
public class StudentA implements Serializable {

    private String name;
    private int sex;
    private TeacherA teacherA;

    public StudentA(String name,int sex,TeacherA teacherA){
        this.name = name;
        this.sex = sex;
        this.teacherA = teacherA;
    }

    @Override
    public String toString() {
        return "StudentA{" +
                "name='" + name + '\'' +
                ", sex=" + sex +
                ", teacherA=" + teacherA +
                '}';
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        TeacherA teacherA = new TeacherA("Arvin", 0);

        StudentA studentA = new StudentA("Tom", 1, teacherA);

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(studentA);
        oos.flush();

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
        StudentA copystu = (StudentA) ois.readObject();

        System.out.println("studentA:"+studentA+",hashCode:"+System.identityHashCode(studentA));
        System.out.println("TeacherA:"+teacherA+",hashCode:"+System.identityHashCode(teacherA));

        System.out.println("copystu:"+copystu+",hashCode:"+System.identityHashCode(copystu));
        System.out.println("copystu teacher:"+copystu.getTeacherA()+",hashCode:"+System.identityHashCode(copystu));

        copystu.setName("copy-Arvin");
        copystu.getTeacherA().setName("copy-tom");

        System.out.println("studentA:"+studentA+",hashCode:"+System.identityHashCode(studentA));
        System.out.println("TeacherA:"+teacherA+",hashCode:"+System.identityHashCode(teacherA));

        System.out.println("copystu:"+copystu+",hashCode:"+System.identityHashCode(copystu));
        System.out.println("copystu teacher:"+copystu.getTeacherA()+",hashCode:"+System.identityHashCode(copystu));
    }
}

運(yùn)行結(jié)果:

studentA:StudentA{name='Tom', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:1304836502
TeacherA:TeacherA{name='Arvin', sex=0},hashCode:1300109446
copystu:StudentA{name='Tom', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:317574433
copystu teacher:TeacherA{name='Arvin', sex=0},hashCode:317574433
studentA:StudentA{name='Tom', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:1304836502
TeacherA:TeacherA{name='Arvin', sex=0},hashCode:1300109446
copystu:StudentA{name='copy-Arvin', sex=1, teacherA=TeacherA{name='copy-tom', sex=0}},hashCode:317574433
copystu teacher:TeacherA{name='copy-tom', sex=0},hashCode:317574433

可以通過很簡潔的代碼即可完美實(shí)現(xiàn)深拷貝冯袍。不過要注意的是匈挖,如果某個(gè)屬性被transient修飾,那么該屬性就無法被拷貝了康愤。
示例:

package com.lvyuanj.core.model;

import lombok.Data;

import java.io.*;

@Data
public class StudentA implements Serializable {

    private transient String name;
    private int sex;
    private TeacherA teacherA;

    public StudentA(String name,int sex,TeacherA teacherA){
        this.name = name;
        this.sex = sex;
        this.teacherA = teacherA;
    }

    @Override
    public String toString() {
        return "StudentA{" +
                "name='" + name + '\'' +
                ", sex=" + sex +
                ", teacherA=" + teacherA +
                '}';
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        TeacherA teacherA = new TeacherA("Arvin", 0);

        StudentA studentA = new StudentA("Tom", 1, teacherA);

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(studentA);
        oos.flush();

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
        StudentA copystu = (StudentA) ois.readObject();

        System.out.println("studentA:"+studentA+",hashCode:"+System.identityHashCode(studentA));
        System.out.println("TeacherA:"+teacherA+",hashCode:"+System.identityHashCode(teacherA));

        System.out.println("copystu:"+copystu+",hashCode:"+System.identityHashCode(copystu));
        System.out.println("copystu teacher:"+copystu.getTeacherA()+",hashCode:"+System.identityHashCode(copystu));

        copystu.setName("copy-Arvin");
        copystu.getTeacherA().setName("copy-tom");

        System.out.println("studentA:"+studentA+",hashCode:"+System.identityHashCode(studentA));
        System.out.println("TeacherA:"+teacherA+",hashCode:"+System.identityHashCode(teacherA));

        System.out.println("copystu:"+copystu+",hashCode:"+System.identityHashCode(copystu));
        System.out.println("copystu teacher:"+copystu.getTeacherA()+",hashCode:"+System.identityHashCode(copystu));
    }
}

運(yùn)行結(jié)果:

studentA:StudentA{name='Tom', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:895328852
TeacherA:TeacherA{name='Arvin', sex=0},hashCode:1304836502
copystu:StudentA{name='null', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:1854731462
copystu teacher:TeacherA{name='Arvin', sex=0},hashCode:1854731462
studentA:StudentA{name='Tom', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:895328852
TeacherA:TeacherA{name='Arvin', sex=0},hashCode:1304836502
copystu:StudentA{name='copy-Arvin', sex=1, teacherA=TeacherA{name='copy-tom', sex=0}},hashCode:1854731462
copystu teacher:TeacherA{name='copy-tom', sex=0},hashCode:1854731462

以上代碼中StudentA中name屬性被transient修飾儡循,copy之后name=null, 打印copystu:StudentA{name='null', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:1854731462

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市征冷,隨后出現(xiàn)的幾起案子择膝,更是在濱河造成了極大的恐慌,老刑警劉巖检激,帶你破解...
    沈念sama閱讀 222,807評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肴捉,死亡現(xiàn)場離奇詭異,居然都是意外死亡叔收,警方通過查閱死者的電腦和手機(jī)齿穗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來饺律,“玉大人窃页,你說我怎么就攤上這事「幢簦” “怎么了脖卖?”我有些...
    開封第一講書人閱讀 169,589評論 0 363
  • 文/不壞的土叔 我叫張陵,是天一觀的道長巧颈。 經(jīng)常有香客問我胚嘲,道長,這世上最難降的妖魔是什么洛二? 我笑而不...
    開封第一講書人閱讀 60,188評論 1 300
  • 正文 為了忘掉前任馋劈,我火速辦了婚禮攻锰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘妓雾。我一直安慰自己娶吞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評論 6 398
  • 文/花漫 我一把揭開白布械姻。 她就那樣靜靜地躺著妒蛇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪楷拳。 梳的紋絲不亂的頭發(fā)上绣夺,一...
    開封第一講書人閱讀 52,785評論 1 314
  • 那天,我揣著相機(jī)與錄音欢揖,去河邊找鬼陶耍。 笑死,一個(gè)胖子當(dāng)著我的面吹牛她混,可吹牛的內(nèi)容都是我干的烈钞。 我是一名探鬼主播,決...
    沈念sama閱讀 41,220評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼坤按,長吁一口氣:“原來是場噩夢啊……” “哼毯欣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起臭脓,我...
    開封第一講書人閱讀 40,167評論 0 277
  • 序言:老撾萬榮一對情侶失蹤酗钞,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后来累,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體砚作,經(jīng)...
    沈念sama閱讀 46,698評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評論 3 343
  • 正文 我和宋清朗相戀三年佃扼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了偎巢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蔼夜。...
    茶點(diǎn)故事閱讀 40,912評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡兼耀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出求冷,到底是詐尸還是另有隱情瘤运,我是刑警寧澤,帶...
    沈念sama閱讀 36,572評論 5 351
  • 正文 年R本政府宣布匠题,位于F島的核電站拯坟,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏韭山。R本人自食惡果不足惜郁季,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評論 3 336
  • 文/蒙蒙 一冷溃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧梦裂,春花似錦似枕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至冗恨,卻和暖如春答憔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背掀抹。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評論 1 274
  • 我被黑心中介騙來泰國打工虐拓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人渴丸。 一個(gè)月前我還...
    沈念sama閱讀 49,359評論 3 379
  • 正文 我出身青樓侯嘀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谱轨。 傳聞我的和親對象是個(gè)殘疾皇子戒幔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評論 2 361

推薦閱讀更多精彩內(nèi)容