Java 淺拷貝和深拷貝

介紹

開發(fā)過(guò)程中,有時(shí)會(huì)遇到把現(xiàn)有的一個(gè)對(duì)象的所有成員屬性拷貝給另一個(gè)對(duì)象的需求崭倘。
比如說(shuō)對(duì)象 A 和對(duì)象 B祥楣,二者都是 ClassC 的對(duì)象,具有成員變量 a 和 b培廓,現(xiàn)在對(duì)對(duì)象 A 進(jìn)行拷貝賦值給 B惹悄,也就是 B.a = A.a; B.b = A.b;

這時(shí)再去改變 B 的屬性 a 或者 b 時(shí),可能會(huì)遇到問題:假設(shè) a 是基礎(chǔ)數(shù)據(jù)類型肩钠,b 是引用類型泣港。
當(dāng)改變 B.a 的值時(shí),沒有問題价匠;
當(dāng)改變 B.b 的值時(shí)当纱,同時(shí)也會(huì)改變 A.b 的值,因?yàn)槠鋵?shí)上面的例子中只是把 A.b 賦值給了 B.b踩窖,因?yàn)槭?b 引用類型的坡氯,所以它們是指向同一個(gè)地址的。這可能就會(huì)給我們使用埋下隱患洋腮。

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

拷貝分類

上面的問題伙狐,其實(shí)就是因?yàn)閷?duì)拷貝的不熟悉導(dǎo)致的涮毫。

根據(jù)對(duì)對(duì)象屬性的拷貝程度(基本數(shù)據(jù)類和引用類型),會(huì)分為兩種:

  • 淺拷貝 (Shallow Copy)
  • 深拷貝 (Deep Copy)

淺拷貝

1. 淺拷貝介紹

淺拷貝是按位拷貝對(duì)象鳞骤,它會(huì)創(chuàng)建一個(gè)新對(duì)象,這個(gè)對(duì)象有著原始對(duì)象屬性值的一份精確拷貝黍判。如果屬性是基本類型豫尽,拷貝的就是基本類型的值;如果屬性是內(nèi)存地址(引用類型)顷帖,拷貝的就是內(nèi)存地址 美旧,因此如果其中一個(gè)對(duì)象改變了這個(gè)地址,就會(huì)影響到另一個(gè)對(duì)象贬墩。即默認(rèn)拷貝構(gòu)造函數(shù)只是對(duì)對(duì)象進(jìn)行淺拷貝復(fù)制(逐個(gè)成員依次拷貝)榴嗅,即只復(fù)制對(duì)象空間而不復(fù)制資源。

2. 淺拷貝特點(diǎn)

(1) 對(duì)于基本數(shù)據(jù)類型的成員對(duì)象陶舞,因?yàn)榛A(chǔ)數(shù)據(jù)類型是值傳遞的嗽测,所以是直接將屬性值賦值給新的對(duì)象。基礎(chǔ)類型的拷貝唠粥,其中一個(gè)對(duì)象修改該值疏魏,不會(huì)影響另外一個(gè)。
(2) 對(duì)于引用類型晤愧,比如數(shù)組或者類對(duì)象大莫,因?yàn)橐妙愋褪且脗鬟f,所以淺拷貝只是把內(nèi)存地址賦值給了成員變量官份,它們指向了同一內(nèi)存空間只厘。改變其中一個(gè),會(huì)對(duì)另外一個(gè)也產(chǎn)生影響舅巷。

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

淺拷貝圖

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

實(shí)現(xiàn)對(duì)象拷貝的類羔味,需要實(shí)現(xiàn) Cloneable 接口,并覆寫 clone() 方法悄谐。

示例如下:

public class Subject {

    private String name;

    public Subject(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "[Subject: " + this.hashCode() + ",name:" + name + "]";
    }
}
public class Student implements Cloneable {

    //引用類型
    private Subject subject;
    //基礎(chǔ)數(shù)據(jù)類型
    private String name;
    private int age;

    public Subject getSubject() {
        return subject;
    }

    public void setSubject(Subject subject) {
        this.subject = subject;
    }

    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;
    }

    /**
     *  重寫clone()方法
     * @return
     */
    @Override
    public Object clone() {
        //淺拷貝
        try {
            // 直接調(diào)用父類的clone()方法
            return super.clone();
        } catch (CloneNotSupportedException e) {
            return null;
        }
    }

    @Override
    public String toString() {
        return "[Student: " + this.hashCode() + ",subject:" + subject + ",name:" + name + ",age:" + age + "]";
    }
}
public class ShallowCopy {
    public static void main(String[] args) {
        Subject subject = new Subject("yuwen");
        Student studentA = new Student();
        studentA.setSubject(subject);
        studentA.setName("Lynn");
        studentA.setAge(20);
        Student studentB = (Student) studentA.clone();
        studentB.setName("Lily");
        studentB.setAge(18);
        Subject subjectB = studentB.getSubject();
        subjectB.setName("lishi");
        System.out.println("studentA:" + studentA.toString());
        System.out.println("studentB:" + studentB.toString());
    }
}

輸出的結(jié)果:

studentA:[Student: 460141958,subject:[Subject: 1163157884,name:lishi],name:Lynn,age:20]
studentB:[Student: 1956725890,subject:[Subject: 1163157884,name:lishi],name:Lily,age:18]

由輸出的結(jié)果可見介评,通過(guò) studentA.clone() 拷貝對(duì)象后得到的 studentB,和 studentA 是兩個(gè)不同的對(duì)象爬舰。studentAstudentB 的基礎(chǔ)數(shù)據(jù)類型的修改互不影響们陆,而引用類型 subject 修改后是會(huì)有影響的。

淺拷貝和對(duì)象拷貝的區(qū)別:

public static void main(String[] args) {
        Subject subject = new Subject("yuwen");
        Student studentA = new Student();
        studentA.setSubject(subject);
        studentA.setName("Lynn");
        studentA.setAge(20);
        Student studentB = studentA;
        studentB.setName("Lily");
        studentB.setAge(18);
        Subject subjectB = studentB.getSubject();
        subjectB.setName("lishi");
        System.out.println("studentA:" + studentA.toString());
        System.out.println("studentB:" + studentB.toString());
    }

這里把 Student studentB = (Student) studentA.clone() 換成了 Student studentB = studentA情屹。
輸出的結(jié)果:

studentA:[Student: 460141958,subject:[Subject: 1163157884,name:lishi],name:Lily,age:18]
studentB:[Student: 460141958,subject:[Subject: 1163157884,name:lishi],name:Lily,age:18]

可見,對(duì)象拷貝后沒有生成新的對(duì)象垃你,二者的對(duì)象地址是一樣的;而淺拷貝的對(duì)象地址是不一樣的惜颇。

深拷貝

1. 深拷貝介紹

通過(guò)上面的例子可以看到,淺拷貝會(huì)帶來(lái)數(shù)據(jù)安全方面的隱患凌摄,例如我們只是想修改了 studentBsubject,但是 studentAsubject 也被修改了锨亏,因?yàn)樗鼈兌际侵赶虻耐粋€(gè)地址。所以器予,此種情況下,我們需要用到深拷貝乾翔。

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

2. 深拷貝特點(diǎn)

(1) 對(duì)于基本數(shù)據(jù)類型的成員對(duì)象浓瞪,因?yàn)榛A(chǔ)數(shù)據(jù)類型是值傳遞的,所以是直接將屬性值賦值給新的對(duì)象乾颁。基礎(chǔ)類型的拷貝英岭,其中一個(gè)對(duì)象修改該值,不會(huì)影響另外一個(gè)(和淺拷貝一樣)诅妹。
(2) 對(duì)于引用類型罚勾,比如數(shù)組或者類對(duì)象,深拷貝會(huì)新建一個(gè)對(duì)象空間吭狡,然后拷貝里面的內(nèi)容尖殃,所以它們指向了不同的內(nèi)存空間。改變其中一個(gè)划煮,不會(huì)對(duì)另外一個(gè)也產(chǎn)生影響送丰。
(3) 對(duì)于有多層對(duì)象的,每個(gè)對(duì)象都需要實(shí)現(xiàn) Cloneable 并重寫 clone() 方法弛秋,進(jìn)而實(shí)現(xiàn)了對(duì)象的串行層層拷貝器躏。
(4) 深拷貝相比于淺拷貝速度較慢并且花銷較大。

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

深拷貝圖

3. 深拷貝的實(shí)現(xiàn)

對(duì)于 Student 的引用類型的成員變量 Subject 蟹略,需要實(shí)現(xiàn) Cloneable 并重寫 clone() 方法登失。

public class Subject implements Cloneable {

    private String name;

    public Subject(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        //Subject 如果也有引用類型的成員屬性,也應(yīng)該和 Student 類一樣實(shí)現(xiàn)
        return super.clone();
    }

    @Override
    public String toString() {
        return "[Subject: " + this.hashCode() + ",name:" + name + "]";
    }
}

Studentclone() 方法中挖炬,需要拿到拷貝自己后產(chǎn)生的新的對(duì)象揽浙,然后對(duì)新的對(duì)象的引用類型再調(diào)用拷貝操作,實(shí)現(xiàn)對(duì)引用類型成員變量的深拷貝茅茂。

public class Student implements Cloneable {

    //引用類型
    private Subject subject;
    //基礎(chǔ)數(shù)據(jù)類型
    private String name;
    private int age;

    public Subject getSubject() {
        return subject;
    }

    public void setSubject(Subject subject) {
        this.subject = subject;
    }

    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;
    }

    /**
     *  重寫clone()方法
     * @return
     */
    @Override
    public Object clone() {
        //深拷貝
        try {
            // 直接調(diào)用父類的clone()方法
            Student student = (Student) super.clone();
            student.subject = (Subject) subject.clone();
            return student;
        } catch (CloneNotSupportedException e) {
            return null;
        }
    }

    @Override
    public String toString() {
        return "[Student: " + this.hashCode() + ",subject:" + subject + ",name:" + name + ",age:" + age + "]";
    }
}

一樣的使用方式

public class ShallowCopy {
    public static void main(String[] args) {
        Subject subject = new Subject("yuwen");
        Student studentA = new Student();
        studentA.setSubject(subject);
        studentA.setName("Lynn");
        studentA.setAge(20);
        Student studentB = (Student) studentA.clone();
        studentB.setName("Lily");
        studentB.setAge(18);
        Subject subjectB = studentB.getSubject();
        subjectB.setName("lishi");
        System.out.println("studentA:" + studentA.toString());
        System.out.println("studentB:" + studentB.toString());
    }
}

輸出結(jié)果:

studentA:[Student: 460141958,subject:[Subject: 1163157884,name:yuwen],name:Lynn,age:20]
studentB:[Student: 1956725890,subject:[Subject: 356573597,name:lishi],name:Lily,age:18]

由輸出結(jié)果可見捏萍,深拷貝后太抓,不管是基礎(chǔ)數(shù)據(jù)類型還是引用類型的成員變量空闲,修改其值都不會(huì)相互造成影響。



參考:

Java 淺拷貝和深拷貝的理解和實(shí)現(xiàn)方式
淺拷貝和深拷貝(談?wù)刯ava中的clone)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末走敌,一起剝皮案震驚了整個(gè)濱河市碴倾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖跌榔,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件异雁,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡僧须,警方通過(guò)查閱死者的電腦和手機(jī)纲刀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門示绊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)暂论,“玉大人,你說(shuō)我怎么就攤上這事展哭》税” “怎么了析恢?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵映挂,是天一觀的道長(zhǎng)柑船。 經(jīng)常有香客問我泼各,道長(zhǎng),這世上最難降的妖魔是什么逆巍? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任锐极,我火速辦了婚禮灵再,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘翎迁。我一直安慰自己,他們只是感情好蒲拉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布全陨。 她就那樣靜靜地躺著衷掷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪雨涛。 梳的紋絲不亂的頭發(fā)上替久,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天蚯根,我揣著相機(jī)與錄音胀糜,去河邊找鬼。 笑死距帅,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的碌秸。 我是一名探鬼主播讥电,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼恩敌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼鸦致!你這毒婦竟也來(lái)了分唾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎折砸,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體两芳,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡怖辆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腻格。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡啥繁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出些楣,到底是詐尸還是另有隱情愁茁,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布鹅很,位于F島的核電站促煮,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏菠齿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望疾棵。 院中可真熱鬧是尔,春花似錦、人聲如沸宣渗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至帮掉,卻和暖如春蟆炊,著一層夾襖步出監(jiān)牢的瞬間瀑志,已是汗流浹背劈猪。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工战得, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留常侦,地道東北人贬媒。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓际乘,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親致讥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子垢袱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

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

  • 1.落筆緣由 突然看到以前的筆記请契,思緒回到了上學(xué)的時(shí)候,滿滿的都是回憶涌韩,以前的不堪與幼稚的想法現(xiàn)在想起來(lái)都會(huì)忍不住...
    lgy_gg閱讀 519評(píng)論 0 2
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,097評(píng)論 1 32
  • 前言 由于最近寫業(yè)務(wù)邏輯的時(shí)候遇到了淺拷貝與深拷貝問題,因此特地記錄一下雇毫。 定義一個(gè)實(shí)體類Person 深拷貝 測(cè)...
    JeremySun0823閱讀 404評(píng)論 0 0
  • 昨天上午婆婆來(lái)我們家了棚放,兒子很高興飘蚯。但是因?yàn)榧依镞€有活要干福也,所以婆婆今天早晨就要回老家。兒子不舍得她走也沒辦法拟杉。 ...
    珍愛_48e5閱讀 158評(píng)論 0 0
  • 1.修行目標(biāo):我要過(guò)內(nèi)圣外王的生活穴店、輕松成功、健康豐盛泣洞,喜樂忧风,擁有氧氣般的金錢,全家每月總收入達(dá)5萬(wàn)球凰,成為成功自由...
    穎儀_5b92閱讀 157評(píng)論 0 0