美文网首页
java 深拷贝

java 深拷贝

作者: 手持棒棒糖 | 来源:发表于2018-08-23 15:33 被阅读0次

1、拷贝与深拷贝

  • 对象的拷贝,就是将原对象中的属性值拷贝到一个同类型(一般来说)的对象中去;
    深拷贝是指在拷贝原对象当中属性值的同时,如果属性是引用类型,那么将该引用类型指向一块新的内存,避免新旧对象引用类型数据修改而相互影响

2、java的Cloneable接口

java对于所有的对象父类Object,提供了一个protected的clone方法,如果我们自己的类需要提供clone功能,需要实现一个空接口: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、深拷贝的实现

1. 实现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);
    }
}

上面这段代码,输出结果为:

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

为何调用clone后创建的对象的值,随着原对象的值的改变而改变了?
由于在MyInteger的clone()方法中,只是简单的调用了super.clone()创建了一个新对象返回,这样,myInteger.myInt与myInteger1_clone.myInt指向的是同一个对象

错误的clone
所以修改myInteger.myInt.i属性的值,自然会导致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
需要循环调用引用对象的正确的clone()方法,一直到基本数据类型或者String
数组对象
一维数组

对于原始数据类型或者String的一维数组,可以直接调用数组的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,没有问题,但是对于引用类型的数组,这样类似的修改会使原对象和新对象的属性同时发生变更

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

对于引用类型的数组,需要在持有数组引用的对象的clone()方法中,对于新数组对象进行递归创建新对象并拷贝原对象的值----可以调用正确的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);
    }
}
二维数组
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]]}

为什么对于基本数据类型,二维数组和一维数组的clone后结果不一致呢
下面是在我电脑上对于二维数组clone后的debug信息


二维数组

通过对象信息可以清晰看到,对于二维数组执行clone(),数组自身引用已经改变,但是数组的第一个维度的引用依旧指向原数组的引用
由于java并不存在真正意义上的二维数组,二维数组的实现是一个以一维数组为元素的一维数组,所以,对于二维数组的clone()行为的理解,与上面对象的一维数组的clone()方式是一致的
其他更高维度的数组同:如果需要对高维数组执行深拷贝,需要最终递归对于构成这个高维数组的每个一维数组执行深拷贝。

2. 拷贝构造器和拷贝工厂

拷贝构造器是一个参数为该类对象的构造器。
拷贝工厂是一个类似于拷贝构造器的静态工厂方法。

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

其本质实现与clon()一致,但是其好处是:
(引自effective java --- 谨慎地覆盖clone)

拷贝构造器的做法,及其静态工厂方法的变形,都比Cloneable/clone方法具有更多的优势,它们不依赖于某
一种很有风险的、语言之外的对象创建机制;它们不要求遵守尚未制定好文档的规范;它们不会与final域的
正常使用发生冲突;它们不会抛出不必要的受检异常(check exception);他们不需要进行类型转换

3. 序列化与反序列化

这个先上的代码,第一次看到是在这种实现是在stackOverFlow,现在已经遍地都是了!
当然,具体原创是谁我不知道。

    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动作
优点:不需要覆盖clone(),不需要单独处理每一个引用的clone行为
缺点:

  1. 需要每一个引用对象实现Serializable接口
不实现Serializable会报java.io.NotSerializableException
  1. 需要深拷贝的属性不能是transient,或者在readObject和writeObject中有对于transient属性的正确处理
  2. readObject和writeObject对于序列化过程的控制影响
    不干预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干预对于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();
        //示例代码,别纠结细节。
        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();
          //示例代码,别纠结细节。
        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() {
        //示例代码,别纠结细节。
        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(),那么需要注意了,如果有特殊值修改,用序列化与反序列化过程来实现深拷贝将不可用

  1. static变量正常情况下是不参与序列化与反序列化的,虽然大部分时候这个不影响结果。
这个算课后作业? 参见《深入理解java虚拟机》,提醒static变量放在哪?
  1. etc.

相关文章

  • java 对象的拷贝

    拷贝:即复制 对象拷贝:即对象复制 java 对象拷贝分类:浅拷贝、深拷贝 java 对象的浅拷贝和深拷贝针对包含...

  • Java基础 - 深拷贝和浅拷贝

    Java 的深拷贝和浅拷贝 什么是深拷贝、浅拷贝 (深克隆、浅克隆)? 在 Java 中,数据类型分为 基本数据类...

  • Java------List的深拷贝与浅拷贝

    Java的浅拷贝(Shallow Copy)、深拷贝(Deep Copy)。 浅拷贝(Shallow Copy) ...

  • java中的深拷贝和浅拷贝

    简单记录一下java中的深拷贝和浅拷贝,深拷贝和浅拷贝只是针对对象而言的. 1 深拷贝代码 2 浅拷贝代码 3 测...

  • java 深拷贝

    1、拷贝与深拷贝 对象的拷贝,就是将原对象中的属性值拷贝到一个同类型(一般来说)的对象中去;深拷贝是指在拷贝原对象...

  • Java深拷贝

    Java对于对象(包括字符串,这点和Swift不同)默认是浅拷贝,体现在直接赋值操作和集合添加操作中,如果要进行深...

  • java 深拷贝

    普通拷贝只拷贝该对象的字段值,该对象里引用的其他对象不进行拷贝,保留原来的引用。所以需要深拷贝。

  • JAVA 深拷贝 浅拷贝

    JAVA 深拷贝 浅拷贝 浅拷贝只是复制了引用, 这个引用还是指向原来的值 深拷贝那就是直接复制了整个Object...

  • Java基础系列-浅拷贝和深拷贝

    原创文章,转载请标注出处:《Java基础系列-浅拷贝和深拷贝》 一、概述 Java中的拷贝功能是由Object类的...

  • java浅拷贝深拷贝

    如果类不实现Clonable接口就重写Object中的clone方法就会抛出CloneNotSupportedEx...

网友评论

      本文标题:java 深拷贝

      本文链接:https://www.haomeiwen.com/subject/alwyiftx.html