java 深拷貝

1、拷貝與深拷貝

  • 對象的拷貝,就是將原對象中的屬性值拷貝到一個同類型(一般來說)的對象中去;
    深拷貝是指在拷貝原對象當(dāng)中屬性值的同時,如果屬性是引用類型,那么將該引用類型指向一塊新的內(nèi)存,避免新舊對象引用類型數(shù)據(jù)修改而相互影響

2、java的Cloneable接口

java對于所有的對象父類Object,提供了一個protected的clone方法,如果我們自己的類需要提供clone功能,需要實現(xiàn)一個空接口:Serializable并重寫Object的clone方法,否則會報:CloneNotSupportedException
示例代碼

public class MyInt implements Cloneable {
    public int i;

    public MyInt(int i) {
        this.i = i;
    }

    /**
     * 在jdk1.5之前,這里只能返回Object
     * 由于jdk1.5之后引入的泛型,所以這里可以返回最終類型
     *
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    protected MyInt clone() throws CloneNotSupportedException {
        return (MyInt) super.clone();
    }

    @Override
    public String toString() {
        return "MyInt{" +
                "i=" + i +
                '}';
    }
}

3、深拷貝的實現(xiàn)

1. 實現(xiàn)Cloneable接口并重寫clone()方法

對于引用
需要正確的重寫引用對象的clone()方法
public class MyInteger implements Cloneable {

    MyInt myInt = new MyInt(0);

    public MyInteger(int i) {
        myInt = new MyInt(i);
    }

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

    @Override
    public String toString() {
        return "MyInteger{" +
                "myInt=" + myInt +
                '}';
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        MyInteger myInteger = new MyInteger(1);
        MyInteger myInteger1_clone = myInteger.clone();

        myInteger.myInt.i = 15;
        System.out.println(myInteger);
        System.out.println(myInteger1_clone);
    }
}

上面這段代碼,輸出結(jié)果為:

MyInteger{myInt=MyInt{i=15}}
MyInteger{myInt=MyInt{i=15}}

為何調(diào)用clone后創(chuàng)建的對象的值,隨著原對象的值的改變而改變了?
由于在MyInteger的clone()方法中,只是簡單的調(diào)用了super.clone()創(chuàng)建了一個新對象返回,這樣,myInteger.myInt與myInteger1_clone.myInt指向的是同一個對象

錯誤的clone

所以修改myInteger.myInt.i屬性的值,自然會導(dǎo)致myInteger1_clone.myInt.i的值隨著一起改變
將上述代碼略微修改:

public class MyInteger implements Cloneable {

    MyInt myInt = new MyInt(0);

    public MyInteger(int i) {
        myInt = new MyInt(i);
    }

    @Override
    protected MyInteger clone() throws CloneNotSupportedException {
        MyInteger clone = (MyInteger) super.clone();
        clone.myInt = new MyInt(this.myInt.i);
        return clone;
    }

    @Override
    public String toString() {
        return "MyInteger{" +
                "myInt=" + myInt +
                '}';
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        MyInteger myInteger = new MyInteger(1);
        MyInteger myInteger1_clone = myInteger.clone();

        myInteger.myInt.i = 15;
        System.out.println(myInteger);
        System.out.println(myInteger1_clone);
    }
}

程序輸出:

MyInteger{myInt=MyInt{i=15}}
MyInteger{myInt=MyInt{i=1}}

debug:

clone

OJBK
需要循環(huán)調(diào)用引用對象的正確的clone()方法,一直到基本數(shù)據(jù)類型或者String

數(shù)組對象
一維數(shù)組

對于原始數(shù)據(jù)類型或者String的一維數(shù)組,可以直接調(diào)用數(shù)組的clone()方法來完成對象的copy

    public static void main(String[] args) {
        String[] strs = {"aa", "bb", "cc"};
        String[] strs_clone = strs.clone();
        strs[1] = "dd";
        System.out.println(Arrays.toString(strs));
        System.out.println(Arrays.toString(strs_clone));
    }

程序輸出:

[aa, dd, cc]
[aa, bb, cc]

OK,沒有問題,但是對于引用類型的數(shù)組,這樣類似的修改會使原對象和新對象的屬性同時發(fā)生變更

public class ArraysClone implements Cloneable {
    MyInt[] myInts = {new MyInt(1), new MyInt(2), new MyInt(3)};

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

    @Override
    public String toString() {
        return "ArraysClone{" +
                "myInts=" + Arrays.toString(myInts) +
                '}';
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        ArraysClone ac = new ArraysClone();
        ArraysClone ac_clone = ac.clone();
        ac.myInts[1].i = 5;
        System.out.println(ac);
        System.out.println(ac_clone);
    }
}

程序輸出

[MyInt{i=1}, MyInt{i=5}, MyInt{i=3}]
[MyInt{i=1}, MyInt{i=5}, MyInt{i=3}]

對于引用類型的數(shù)組,需要在持有數(shù)組引用的對象的clone()方法中,對于新數(shù)組對象進行遞歸創(chuàng)建新對象并拷貝原對象的值----可以調(diào)用正確的clone()

public class ArraysClone implements Cloneable {
    MyInt[] myInts = {new MyInt(1), new MyInt(2), new MyInt(3)};

    @Override
    protected ArraysClone clone() throws CloneNotSupportedException {
        ArraysClone clone = (ArraysClone) super.clone();
        clone.myInts = new MyInt[this.myInts.length];
        for (int i = 0; i < this.myInts.length; i++) {
            clone.myInts[i] = this.myInts[i].clone();
        }
        return clone;
    }

    @Override
    public String toString() {
        return "ArraysClone{" +
                "myInts=" + Arrays.toString(myInts) +
                '}';
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        ArraysClone ac = new ArraysClone();
        ArraysClone ac_clone = ac.clone();
        ac.myInts[1].i = 5;
        System.out.println(ac);
        System.out.println(ac_clone);
    }
}
二維數(shù)組
public class MultiArraysClone implements Cloneable {

    int[][] intss = {{10, 11}, {20, 21}, {30, 31}};

    @Override
    public String toString() {
        return "MultiArraysClone{" +
                "intss=" + Arrays.deepToString(intss) +
                '}';
    }

    @Override
    protected MultiArraysClone clone() throws CloneNotSupportedException {
        MultiArraysClone clone = (MultiArraysClone) super.clone();
        clone.intss = this.intss.clone();
        return clone;
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        MultiArraysClone multiArraysClone = new MultiArraysClone();
        MultiArraysClone multiArraysClone_clone = multiArraysClone.clone();
        multiArraysClone.intss[1][0] = 99;
        multiArraysClone.intss[1][1] = 99;
        System.out.println(multiArraysClone);
        System.out.println(multiArraysClone_clone);
    }
}

程序輸出

MultiArraysClone{intss=[[10, 11], [99, 99], [30, 31]]}
MultiArraysClone{intss=[[10, 11], [99, 99], [30, 31]]}

為什么對于基本數(shù)據(jù)類型,二維數(shù)組和一維數(shù)組的clone后結(jié)果不一致呢
下面是在我電腦上對于二維數(shù)組clone后的debug信息


二維數(shù)組

通過對象信息可以清晰看到,對于二維數(shù)組執(zhí)行clone(),數(shù)組自身引用已經(jīng)改變,但是數(shù)組的第一個維度的引用依舊指向原數(shù)組的引用
由于java并不存在真正意義上的二維數(shù)組,二維數(shù)組的實現(xiàn)是一個以一維數(shù)組為元素的一維數(shù)組,所以,對于二維數(shù)組的clone()行為的理解,與上面對象的一維數(shù)組的clone()方式是一致的
其他更高維度的數(shù)組同:如果需要對高維數(shù)組執(zhí)行深拷貝,需要最終遞歸對于構(gòu)成這個高維數(shù)組的每個一維數(shù)組執(zhí)行深拷貝。

2. 拷貝構(gòu)造器和拷貝工廠

拷貝構(gòu)造器是一個參數(shù)為該類對象的構(gòu)造器。
拷貝工廠是一個類似于拷貝構(gòu)造器的靜態(tài)工廠方法。

public class Foo {

    MyInt[] myInts = {new MyInt(1), new MyInt(2), new MyInt(3)};

    public Foo() {
    }

    public Foo(Foo src) {
        for (int i=0; i<src.myInts.length; i++) {
            this.myInts[i] = new MyInt(src.myInts[i].i);
        }
    }

    public static Foo deepCopy(Foo src) {
        Foo dest = new Foo();
        for (int i=0; i<src.myInts.length; i++) {
            dest.myInts[i] = new MyInt(src.myInts[i].i);
        }
        return dest;
    }

    @Override
    public String toString() {
        return "Foo{" +
                "myInts=" + Arrays.toString(myInts) +
                '}';
    }

    public static void main(String[] args) {
        Foo src = new Foo();
        Foo constructor_clone = new Foo(src);
        Foo factory_clone = Foo.deepCopy(src);
        src.myInts[1].i = 99;
        System.out.println(src);
        System.out.println(constructor_clone);
        System.out.println(factory_clone);
    }
}

其本質(zhì)實現(xiàn)與clon()一致,但是其好處是:
(引自effective java --- 謹(jǐn)慎地覆蓋clone)

拷貝構(gòu)造器的做法,及其靜態(tài)工廠方法的變形,都比Cloneable/clone方法具有更多的優(yōu)勢,它們不依賴于某
一種很有風(fēng)險的、語言之外的對象創(chuàng)建機制;它們不要求遵守尚未制定好文檔的規(guī)范;它們不會與final域的
正常使用發(fā)生沖突;它們不會拋出不必要的受檢異常(check exception);他們不需要進行類型轉(zhuǎn)換

3. 序列化與反序列化

這個先上的代碼,第一次看到是在這種實現(xiàn)是在stackOverFlow,現(xiàn)在已經(jīng)遍地都是了!
當(dāng)然,具體原創(chuàng)是誰我不知道。

    public static <T extends Serializable> T deepCopy(T src) throws IOException, ClassNotFoundException {
        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(byteOut);
        out.writeObject(src);

        ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
        ObjectInputStream in = new ObjectInputStream(byteIn);
        @SuppressWarnings("unchecked")
        T dest = (T) in.readObject();
        return dest;
    }

通過序列化與反序列化操作完成deepcopy動作
優(yōu)點:不需要覆蓋clone(),不需要單獨處理每一個引用的clone行為
缺點:

  1. 需要每一個引用對象實現(xiàn)Serializable接口
不實現(xiàn)Serializable會報java.io.NotSerializableException
  1. 需要深拷貝的屬性不能是transient,或者在readObject和writeObject中有對于transient屬性的正確處理
  2. readObject和writeObject對于序列化過程的控制影響
    不干預(yù)transient的序列化過程:
public class DeepCloneBySerizalizable implements Serializable {
    int[][] intss = {{10, 11}, {20, 21}, {30, 31}};
    // 由于不特殊處理,transient不會序列化,所以會在clone對象中打印null
    transient int[][] intss1 = {{10, 11}, {20, 21}, {30, 31}};


    @Override
    public String toString() {
        return "DeepCloneBySerizalizable{" +
                "\nintss=" + Arrays.deepToString(intss) + ", " +
                "\nintss1=" + Arrays.deepToString(intss1) + ", " +
                "\n}\n\n";
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        DeepCloneBySerizalizable dcbs = new DeepCloneBySerizalizable();
        DeepCloneBySerizalizable dcbs_clone = deepCopy(dcbs);

        dcbs.intss[1][0] = 99;
        dcbs.intss[1][1] = 99;

        dcbs.intss1[1][0] = 999;
        dcbs.intss1[1][1] = 999;

        System.out.println(dcbs);
        System.out.println("-------------------------------------------\n\n");
        System.out.println(dcbs_clone);
    }
}

程序輸出:

DeepCloneBySerizalizable{
intss=[[10, 11], [99, 99], [30, 31]], 
intss1=[[10, 11], [999, 999], [30, 31]], 
}
-------------------------------------------
DeepCloneBySerizalizable{
intss=[[10, 11], [20, 21], [30, 31]], 
intss1=null, 
}

用readObject和writeObject干預(yù)對于transient對象的序列化與反序列化過程:

public class DeepCloneBySerizalizable implements Serializable {
    int[][] intss = {{10, 11}, {20, 21}, {30, 31}};
    // 由于不特殊處理,transient不會序列化,所以會在clone對象中打印null
    transient int[][] intss1 = {{10, 11}, {20, 21}, {30, 31}};


    @Override
    public String toString() {
        return "DeepCloneBySerizalizable{" +
                "\nintss=" + Arrays.deepToString(intss) + ", " +
                "\nintss1=" + Arrays.deepToString(intss1) + ", " +
                "\n}";
    }

    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();
        //示例代碼,別糾結(jié)細節(jié)。
        oos.writeInt(intss1.length);
        for (int i = 0; i < intss1.length; i++) {
            oos.writeObject(intss1[i]);
        }
    }

    private void readObject(ObjectInputStream ois) throws IOException,
            ClassNotFoundException {
        ois.defaultReadObject();
          //示例代碼,別糾結(jié)細節(jié)。
        int length = ois.readInt();
        this.intss1 = new int[3][2];
        for (int i = 0; i < length; i++) {
            this.intss1[i] = (int[]) ois.readObject();
        }
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        DeepCloneBySerizalizable dcbs = new DeepCloneBySerizalizable();
        DeepCloneBySerizalizable dcbs_clone = deepCopy(dcbs);

        dcbs.intss[1][0] = 99;
        dcbs.intss[1][1] = 99;

        dcbs.intss1[1][0] = 999;
        dcbs.intss1[1][1] = 999;

        System.out.println(dcbs);
        System.out.println("-------------------------------------------");
        System.out.println(dcbs_clone);
    }
}

程序輸出:

DeepCloneBySerizalizable{
intss=[[10, 11], [99, 99], [30, 31]], 
intss1=[[10, 11], [999, 999], [30, 31]], 
}
-------------------------------------------
DeepCloneBySerizalizable{
intss=[[10, 11], [20, 21], [30, 31]], 
intss1=[[10, 11], [20, 21], [30, 31]], 
}
  1. readResolve對于反序列化對象的影響
public class DeepCloneBySerizalizable implements Serializable {
    int[][] intss = {{10, 11}, {20, 21}, {30, 31}};
    // 由于不特殊處理,transient不會序列化,所以會在clone對象中打印null
    transient int[][] intss1 = {{10, 11}, {20, 21}, {30, 31}};

    @Override
    public String toString() {
        return "DeepCloneBySerizalizable{" +
                "\nintss=" + Arrays.deepToString(intss) + ", " +
                "\nintss1=" + Arrays.deepToString(intss1) + ", " +
                "\n}";
    }

    private Object readResolve() {
        //示例代碼,別糾結(jié)細節(jié)。
        DeepCloneBySerizalizable deepCloneBySerizalizable = new DeepCloneBySerizalizable();
        deepCloneBySerizalizable.intss1[1][1] = -123;
        return deepCloneBySerizalizable;
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        DeepCloneBySerizalizable dcbs = new DeepCloneBySerizalizable();
        DeepCloneBySerizalizable dcbs_clone = deepCopy(dcbs);

        dcbs.intss[1][0] = 99;
        dcbs.intss[1][1] = 99;

        dcbs.intss1[1][0] = 999;
        dcbs.intss1[1][1] = 999;

        System.out.println(dcbs);
        System.out.println("-------------------------------------------");
        System.out.println(dcbs_clone);
    }
}

輸出:

DeepCloneBySerizalizable{
intss=[[10, 11], [99, 99], [30, 31]], 
intss1=[[10, 11], [999, 999], [30, 31]], 
}
-------------------------------------------
DeepCloneBySerizalizable{
intss=[[10, 11], [20, 21], [30, 31]], 
intss1=[[10, 11], [20, -123], [30, 31]], 
}

由于可以通過readResolve()方法修改反序列化后對象屬性,所以,如果進行深拷貝涉及的對象有readResolve(),那么需要注意了,如果有特殊值修改,用序列化與反序列化過程來實現(xiàn)深拷貝將不可用

  1. static變量正常情況下是不參與序列化與反序列化的,雖然大部分時候這個不影響結(jié)果。
這個算課后作業(yè)? 參見《深入理解java虛擬機》,提醒static變量放在哪?
  1. etc.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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