Java基礎 - 深拷貝和淺拷貝

Java 的深拷貝和淺拷貝

什么是深拷貝贸伐、淺拷貝 (深克隆勾给、淺克隆)恍涂?

在 Java 中,數(shù)據(jù)類型分為 基本數(shù)據(jù)類型引用數(shù)據(jù)類型足丢,深/淺拷貝是針對于 引用數(shù)據(jù)類型 來說的粱腻。

  • 淺拷貝:對基本數(shù)據(jù)類型進行值傳遞,對引用數(shù)據(jù)類型進行引用傳遞般的拷貝稱為淺拷貝斩跌。通過實現(xiàn)Cloneable接口并重寫Object類中的clone()方法可以實現(xiàn)淺克隆绍些。

    淺拷貝.png

  • 深拷貝:對基本數(shù)據(jù)類型進行值傳遞,對引用數(shù)據(jù)類型耀鸦,創(chuàng)建一個新的對象柬批,并復制其內(nèi)容稱為深拷貝。

    深拷貝.png

如何進行深拷貝袖订?

  • 實現(xiàn)Cloneable接口并重寫Object類及成員變量中引用類型的的clone()方法
  • 實現(xiàn)Serializable接口氮帐,通過對象的序列化和反序列化實現(xiàn)克隆,可以實現(xiàn)真正的深度克隆

注意:基于序列化和反序列化實現(xiàn)的拷貝不僅僅是深度克隆洛姑,更重要的是通過泛型限定上沐,可以檢查出要克隆的對象是否支持序列化,這項檢查是編譯器完成的楞艾,不是在運行時拋出異常参咙,這種是方案明顯優(yōu)于使用Object類的clone方法克隆對象龄广。讓問題在編譯的時候暴露出來總是優(yōu)于把問題留到運行時。

舉個栗子

實現(xiàn)Cloneable接口 : 淺克隆 vs 深克隆
  • 淺克隆
@Getter
@AllArgsConstructor
public class Sub {
    private String sName;
}

@AllArgsConstructor
public class Parent implements Cloneable {
    private String pName;
    private Sub sub;

    @Override
    protected Object clone() {
        Parent parent = null;

        try {
            parent = (Parent) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return parent;
    }

    public static void main(String[] args) {
        Sub sub = new Sub("sName");
        Parent parent = new Parent("pName", sub);
        Parent clone = (Parent) parent.clone();

        System.out.println("==============parent===================");
        System.out.println(parent.hashCode());
        System.out.println(parent.pName);
        System.out.println(parent.sub.hashCode());
        System.out.println(parent.sub.getSName());

        System.out.println("================clone===================");
        System.out.println(clone.hashCode());
        System.out.println(clone.pName);
        System.out.println(clone.sub.hashCode());
        System.out.println(clone.sub.getSName());

        System.out.println("=================hashcode================");
        System.out.println(parent.hashCode() == clone.hashCode());
        System.out.println(parent.sub.hashCode() == clone.sub.hashCode());
    }
}

輸出:
==============parent===================
166239592
pName
991505714
sName
================clone===================
385242642
pName
991505714
sName
=================hashcode================
false
true
  • 深克隆
@Getter
@AllArgsConstructor
public class Sub implements Cloneable {
    private String sName;

    @Override
    protected Sub clone() {
        Sub sub = null;
        try {
            sub = (Sub) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return sub;
    }
}

@AllArgsConstructor
public class Parent implements Cloneable {
    private String pName;
    private Sub sub;

    @Override
    protected Object clone() {
        Parent parent = null;

        try {
            parent = (Parent) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        assert parent != null;
        parent.sub = sub.clone();
        return parent;
    }

    public static void main(String[] args) {
        Sub sub = new Sub("sName");
        Parent parent = new Parent("pName", sub);
        Parent clone = (Parent) parent.clone();

        System.out.println("==============parent===================");
        System.out.println(parent.hashCode());
        System.out.println(parent.pName);
        System.out.println(parent.sub.hashCode());
        System.out.println(parent.sub.getSName());

        System.out.println("================clone===================");
        System.out.println(clone.hashCode());
        System.out.println(clone.pName);
        System.out.println(clone.sub.hashCode());
        System.out.println(clone.sub.getSName());

        System.out.println("=================hashcode================");
        System.out.println(parent.hashCode() == clone.hashCode());
        System.out.println(parent.sub.hashCode() == clone.sub.hashCode());
    }
}


輸出:
==============parent===================
166239592
pName
991505714
sName
================clone===================
385242642
pName
824009085
sName
=================hashcode================
false
false
實現(xiàn)Serializable接口蕴侧,深克隆
public class CloneUtils {
    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T clone(T obj){
        T cloneObj = null;
        try {
            //寫入字節(jié)流
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ObjectOutputStream obs = new ObjectOutputStream(out);
            obs.writeObject(obj);
            obs.close();

            //分配內(nèi)存择同,寫入原始對象,生成新對象
            ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(ios);

            //返回生成的新對象
            cloneObj = (T) ois.readObject();
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cloneObj;
    }
}

@Getter
@AllArgsConstructor
public class Sub implements Serializable {
    private String sName;
}

@AllArgsConstructor
public class Parent implements Serializable {
    private String pName;
    private Sub sub;

    public static void main(String[] args) {
        Sub sub = new Sub("sName");
        Parent parent = new Parent("pName", sub);
        Parent clone = CloneUtils.clone(parent);

        System.out.println("==============parent===================");
        System.out.println(parent.hashCode());
        System.out.println(parent.pName);
        System.out.println(parent.sub.hashCode());
        System.out.println(parent.sub.getSName());

        System.out.println("================clone===================");
        System.out.println(clone.hashCode());
        System.out.println(clone.pName);
        System.out.println(clone.sub.hashCode());
        System.out.println(clone.sub.getSName());

        System.out.println("=================hashcode================");
        System.out.println(parent.hashCode() == clone.hashCode());
        System.out.println(parent.sub.hashCode() == clone.sub.hashCode());
    }
}

輸出:

==============parent===================
708049632
pName
716083600
sName
================clone===================
1791930789
pName
762152757
sName
=================hashcode================
false
false

擴展

  • 標識接口 : Java語言提供的Cloneable接口和Serializable接口的代碼非常簡單净宵,它們都是空接口敲才,這種空接口也稱為標識接口,標識接口中沒有任何方法的定義塘娶,其作用是告訴JRE這些接口的實現(xiàn)類是否具有某個功能归斤,如是否支持克隆痊夭、是否支持序列化等刁岸。

參考

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末她我,一起剝皮案震驚了整個濱河市虹曙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌番舆,老刑警劉巖酝碳,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異恨狈,居然都是意外死亡疏哗,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門禾怠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來返奉,“玉大人,你說我怎么就攤上這事吗氏⊙科” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵弦讽,是天一觀的道長污尉。 經(jīng)常有香客問我,道長往产,這世上最難降的妖魔是什么被碗? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮仿村,結果婚禮上锐朴,老公的妹妹穿的比我還像新娘。我一直安慰自己奠宜,他們只是感情好包颁,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布瞻想。 她就那樣靜靜地躺著,像睡著了一般娩嚼。 火紅的嫁衣襯著肌膚如雪蘑险。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天岳悟,我揣著相機與錄音佃迄,去河邊找鬼。 笑死贵少,一個胖子當著我的面吹牛呵俏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播滔灶,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼普碎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了录平?” 一聲冷哼從身側(cè)響起麻车,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎斗这,沒想到半個月后动猬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡表箭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年赁咙,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片免钻。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡彼水,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出伯襟,到底是詐尸還是另有隱情猿涨,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布姆怪,位于F島的核電站叛赚,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏稽揭。R本人自食惡果不足惜俺附,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望溪掀。 院中可真熱鬧事镣,春花似錦、人聲如沸揪胃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至随闪,卻和暖如春阳似,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背铐伴。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工撮奏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人当宴。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓畜吊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親户矢。 傳聞我的和親對象是個殘疾皇子玲献,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

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