5.4 深拷贝(Deep Copy)

    通过上述学习,我们知道Java提供了浅拷贝的方法,那么,如何实现一个深拷贝呢?一般情况下,我们有两种方式来实现。

    1.复制对象时,递归地调用属性对象的克隆方法。读者可以根据具体的类,撰写出实现特定类型的深拷贝方法。

    一般来说,我们很难实现一个一般性的方法来完成任何类型对象的深拷贝。有人根据反射得到属性的类型,然后依照它的类型构造对象,但前提是,这些属性的类型必须含有一个公有的默认构造方法,否则作为一个一般性的方法,很难确定传递给非默认构造方法的参数值;此外,如果属性类型是接口或者抽象类型,必须提供查找到相关的具体类方法,作为一个一般性的方法,这个也很难办到。

    2.如果类实现了java.io.Serializable接口,把原型对象序列化,然后反序列化后得到的对象,其实就是一个新的深拷贝对象。

    我们给出第二种方法的实现,代码片段大致如下所示。

    figure_0076_0050

    figure_0077_0051

    代码注解

    DeepCopyBean实现了java.io.Serializable接口,它含有一个原始类型(Primitive Type)的属性primitiveField和对象属性objectField。

    此类的deepCopy()方法首先序列化自己到流中,然后从流中反序列化,得到的对象便是一个新的深拷贝。

    为了验证是不是实现了深拷贝,我们编写了如下测试代码。

    figure_0077_0052

    figure_0078_0053

    代码注解

    originalBean. setObjectField(new String("123456"))和originalBean.set ObjectField("123456")两句代码是不一样的,前者创建了两个String对象,其中一个是在JVM的字符串池(String pool)里,另外一个在堆中,并且属性引用指向的对象在堆里。后者属性引用指向了JVM字符串池中的“123456”对象。

    我们知道,如果是浅拷贝,即引用指向同一内存地址,则newBean.getObject Field()==originalBean.getObjectField()为true,如果是深拷贝,则创建了不同对象,引用指向的地址肯定不一样,即此值应为false。但是这两种方式,使用这句newBean.getObjectField().equals(originalBean.getObjectField())进行比较,其结果必须为true,测试结果如下所示。

    figure_0078_0054

    和我们预想的结果一样,原始类型使用==进行比较,结果相等,而引用类型使用==比较,结果显示未指向相同的地址,但是使用equals()方法比较的结果为true,即证明我们实现了深拷贝。

    使用这种方式进行深拷贝,一方面,它只能复制实现Serializable接口类型的对象,其属性也是可序列化的;另一方面,序列化和反序列化比较耗时。

    选用此方式实现深拷贝时需要做这两方面的权衡。