理解Java淺克隆和深克隆

克隆概念

Java一切皆對(duì)象,克隆就是對(duì)對(duì)象的克?。豢寺】赡苈?tīng)起來(lái)有點(diǎn)高級(jí),也可以為對(duì)象復(fù)制或者對(duì)象拷貝。
平時(shí)開(kāi)發(fā)中,什么時(shí)候需要用到對(duì)象復(fù)制呢?當(dāng)你有一個(gè)實(shí)體類,有很多屬性,并且很多屬性已經(jīng)賦了值,這個(gè)時(shí)候需要對(duì)這個(gè)對(duì)象進(jìn)行修改操作,但后面還會(huì)用到原來(lái)的值,這時(shí)就需要對(duì)象復(fù)制。

淺克隆

用代碼舉個(gè)栗子先:

public static class C implements Cloneable{
    String name;

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

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

@Test
public void test2() throws CloneNotSupportedException {
    C c = new C();
    c.name = "Cat";
    System.out.println(c + " hashCode: " + c.hashCode());
    System.out.println(c.name + " hashCode: " + c.name.hashCode());

    C copy = (C) c.clone();
    System.out.println(copy + " hashCode: " + copy.hashCode());
    System.out.println(copy.name + " hashCode: " + copy.name.hashCode());

    c.name = "Dog";
    System.out.println(c + " hashCode: " + c.hashCode());
    System.out.println(copy + " hashCode: " + copy.hashCode());
}

test2方法是個(gè)單元測(cè)試方法,運(yùn)行結(jié)果:
C{name='Cat'} hashCode: 458209687
Cat hashCode: 67510
C{name='Cat'} hashCode: 233530418
Cat hashCode: 67510
C{name='Dog'} hashCode: 458209687
C{name='Cat'} hashCode: 233530418

clone()是Object的方法, 子類需要實(shí)現(xiàn)Cloneable接口,不實(shí)現(xiàn)調(diào)用會(huì)拋CloneNotSupportedException異常。
首先我定義了一個(gè)類C, c有個(gè)成員變量name,是String類型的。test2方法里,先創(chuàng)建一個(gè)實(shí)例c,給實(shí)例的name賦值為Cat,接著打印c和c.name的hashCode; 然后用c克隆一個(gè)實(shí)例賦值給copy, 答應(yīng)copy和copy.name的hashCode,對(duì)比c和copy的hashCode, 發(fā)現(xiàn)c和copy的hashCode是不同的,說(shuō)明它倆指向的是不同的兩個(gè)實(shí)例,在堆內(nèi)存中是有2塊區(qū)域。對(duì)比c.name和copy.name,發(fā)現(xiàn)它倆的hashCode是相同的. 接著吧c.name重新賦值,賦值為Dog,再看打印的log,沒(méi)有影響到copy的里name屬性。

再看另外一個(gè)栗子:

public static class A implements Cloneable{
    B b;

    @Override
    public String toString() {
        return "A{" +
                "b=" + b +
                '}';
    }

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

public static class B implements Cloneable{
    String name;

    @Override
    public String toString() {
        return "B{" +
                "name=" + name +
                '}';
    }
}

@Test
public void test() throws CloneNotSupportedException {
    A a = new A();
    B b = new B();
    b.name = "Cat";
    a.b = b;
    System.out.println(a + " hashCode: " + a.hashCode());
    System.out.println(a.b + " hashCode: " + a.b.hashCode());

    A copy = (A) a.clone();
    System.out.println(copy + " hashCode: " + copy.hashCode());
    System.out.println(copy.b + " hashCode: " + copy.b.hashCode());

    b.name = "Dog";
    System.out.println(a + " hashCode: " + a.hashCode());
    System.out.println(copy + " hashCode: " + copy.hashCode());
}


運(yùn)行的結(jié)果:
A{b=B{name=Cat}} hashCode: 458209687
B{name=Cat} hashCode: 233530418
A{b=B{name=Cat}} hashCode: 683287027
B{name=Cat} hashCode: 233530418
A{b=B{name=Dog}} hashCode: 458209687
A{b=B{name=Dog}} hashCode: 683287027

前一個(gè)例子中類C中有個(gè)String的成員變量, 而這個(gè)例子中類A中有個(gè)自定義的類B成員變量, 但你會(huì)發(fā)現(xiàn)創(chuàng)建A實(shí)例, 復(fù)制一個(gè)A實(shí)例后,修改a成員變量b的name為Dog, 看日志copy里b的name也跟著改變啦。還沒(méi)修改為Dog之前,a和copy的hashCode是不同的,說(shuō)明它倆指向的是不同的兩個(gè)實(shí)例,在堆內(nèi)存中是有2塊區(qū)域;a.b和copy.b的hashCode是相同的,它倆指向堆內(nèi)存中的同一區(qū)域,所以當(dāng)你修改a.b.name的值, copy.b.name肯定也跟著改變。

Object的clone方法只是淺克隆, 第一個(gè)例子中, c.name = "Dog" 等同于c.name = new String("Dog"),
c.name的引用指向已經(jīng)發(fā)生了改變, 這時(shí)c.name和copy.name它倆指向的是不同的兩個(gè)實(shí)例。第二個(gè)例子中,a.b和copy.b始終都是同一個(gè)引用,
如果改為 B b1 = new B(); a.b = b1; b1.name = "Dog", 這樣子就跟第一個(gè)例子相同。

淺克隆,對(duì)于被克隆的類中成員變量都是基本數(shù)據(jù)類型,可以實(shí)現(xiàn)了兩份數(shù)據(jù);被克隆的類中成員變量是對(duì)象類型,那么這個(gè)成員變量還是原來(lái)的引用,修改為新對(duì)象的值,舊對(duì)象的該對(duì)象類型的成員變量還是會(huì)變化。

深克隆

深克隆, 被克隆的類中成員變量無(wú)論是什么類型, 都可以實(shí)現(xiàn)了兩份數(shù)據(jù);

深克隆主要有兩個(gè)方式實(shí)現(xiàn):

  • 重寫(xiě)clone方法
  • 序列化與反序列化
重寫(xiě)clone方法

重寫(xiě)clone方法實(shí)現(xiàn)深克隆比較麻煩,要對(duì)所有是對(duì)象類型的成員變量,進(jìn)行重新創(chuàng)建實(shí)例,重新賦值;
如果集合類更麻煩,比如說(shuō)ArrayList<Model>, ArrayList的重寫(xiě)了clone(), 但還是淺克隆, 要實(shí)現(xiàn)深克隆需要遍歷所有Model,創(chuàng)建實(shí)例,重新賦值。

下面代碼是對(duì)上面淺克隆第二個(gè)兩字進(jìn)行了深克隆的實(shí)現(xiàn), 重寫(xiě)類A的clone方法:

public static class A implements Cloneable{
    B b;

    @Override
    public String toString() {
        return "A{" +
                "b=" + b +
                '}';
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        A a = (A) super.clone();
        B b = new B();
        b.name = this.b.name;
        a.b = b;
        return a;
    }
}

序列化與反序列化

序列化與反序列化實(shí)現(xiàn)深克隆方式很多,你可以將實(shí)例轉(zhuǎn)成json的字符串,再將字符串轉(zhuǎn)回實(shí)例,相當(dāng)于復(fù)制了一份;你也可以O(shè)bjectOutputStream和ObjectInputStream實(shí)現(xiàn); 在安卓上,可以利用Parcelable實(shí)現(xiàn)等等。
這種方式相對(duì)于重寫(xiě)clone方法實(shí)現(xiàn)可能會(huì)簡(jiǎn)單點(diǎn),但性能上會(huì)差很多。

下面就簡(jiǎn)單利用ObjectOutputStream和ObjectInputStream實(shí)現(xiàn),代碼如下:

static  <T extends Serializable> T copy(T  origin) {
    ByteArrayInputStream in = null;
    ByteArrayOutputStream pos = null;
    T copyList = null;
    try {
        pos = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(pos);
        out.writeObject(origin);
        in = new ByteArrayInputStream(pos.toByteArray());
        ObjectInputStream oin = new ObjectInputStream(in);
        copyList = (T) oin.readObject();
    } catch (Exception ignored) {
    }finally {
        if(in != null){
            try {
                in.close();
            } catch (IOException ignored) {
            }
        }
        if(pos != null){
            try {
                pos.close();
            } catch (IOException ignored) {
            }
        }
    }
    return copyList;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 34,692評(píng)論 18 399
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,554評(píng)論 19 139
  • 1.項(xiàng)目經(jīng)驗(yàn) 2.基礎(chǔ)問(wèn)題 3.指南認(rèn)識(shí) 4.解決思路 ios開(kāi)發(fā)三大塊: 1.Oc基礎(chǔ) 2.CocoaTouch...
    扶光啟玄閱讀 5,204評(píng)論 0 13
  • 1. 每個(gè)月讀一本英文原版書(shū)籍 8 月份-- One up the Wall Street by Peter ...
    33勇敢出發(fā)閱讀 83評(píng)論 0 0
  • 感覺(jué)題目又不能免俗的雞湯了。 Hey,2016. Aurevoir,2015. 但愿我腦子里蹦出的唯一一個(gè)法語(yǔ)單詞...
    徐嚒嚒嚒嚒閱讀 253評(píng)論 1 0

友情鏈接更多精彩內(nèi)容