第二十三周_T-Java 实现深拷贝

83 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 14 天,点击查看活动详情

背景

在网上和书上看到的一个例子:Java 如何实现深拷贝,有哪些方式?当然也是 Java 基础知识及面试题。

浅拷贝 VS 深拷贝

浅拷贝

如果原型对象的成员变量是值类型,将复制一份给克隆对象,也就是说在堆中拥有独立的空间;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。换句话说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。

深拷贝

深拷贝是一种完全拷贝,无论是值类型还是引用类型都会完完全全的拷贝一份,在内存中生成一个新的对象

深拷贝实现方式

实现 Cloneable 接口

浅拷贝

省略了 get/set 、构造函数、toString 方法。

public class CloneTest implements Cloneable {
    int age;
    String name;
    Address address;

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

class Address implements Cloneable {
    String prov;

}   
   public static void main(String[] args) throws CloneNotSupportedException {
        CloneTest cloneTest1 = new CloneTest(10, "nihy", new Address("shanghai"));
        CloneTest cloneTest2 = (CloneTest) cloneTest1.clone();
        cloneTest2.getAddress().setProv("chongqing");
        cloneTest2.setName("chongqing");
        cloneTest2.setAge(0);
        System.out.println(cloneTest1);
        System.out.println(cloneTest2);
   }

打印:

CloneTest{age=10, name='nihy', address=Address{prov='chongqing'}}

CloneTest{age=0, name='chongqing', address=Address{prov='chongqing'}}

没有对 clone 方法重写,就是浅拷贝。对输出内容解释:

int age基本数据类型,值传递,改变拷贝对原内容无影响
String name不可变对象,在修改的时候,会新创建一个对象,所以对原对象没有影响
Address address引用类型,指向同一个地址,值改变了,原始值也会改变。

深拷贝

需要重写 clone() 接口

    @Override
    protected Object clone() throws CloneNotSupportedException {
        CloneTest cloneTest = (CloneTest) super.clone();
        cloneTest.setAddress((Address) address.clone());
        return cloneTest;
    }

打印:此时对象的引用也会拷贝

CloneTest{age=10, name='nihy', address=Address{prov='shanghai'}}

CloneTest{age=0, name='chongqing', address=Address{prov='chongqing'}}

序列化

上面的实体类不变,只是把实现 Cloneable 接口换成实现 Serializable 接口,并且多实现一个序列化方法:

   //<T extends Serializable> :参数 T 必须实现 Serializable
    public static <T extends Serializable> T clone(T obj) {
        T objClone = null;
        try {
            //写入字节流
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ObjectOutputStream obs = new ObjectOutputStream(out);
            obs.writeObject(obj);
            obs.close();

            //分配内存,写入原始对象,生成新对象
            ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(ios);
            //返回生成的新对象
            objClone = (T) ois.readObject();
            ois.close();
        } catch (Exception e) {
            System.out.println(e);
        }
        return objClone;
    }

具体调用的代码:

  • 自己实现的代码和 Apache 的 SerializationUtils.clone() 可以说一模一样的。所以直接使用工具包就好
  • JSON 解析,比如 FastJSON、jackjson 等
    public static void main(String[] args) {
//        JSONObject.parseObject();
        SerializeTest serializeTest=new SerializeTest(27,"nihy",new AddressTest("shanghai"));

        //1. ByteArrayOutputStream、ByteArrayInputStream 实现流的写入写出
//        SerializeTest serializeTest1 = clone(serializeTest);

        //2. 使用此方式 JSON.parseObject 涉及到的实体类都必须是 public 定义的。否则报错 instance error
        SerializeTest serializeTest1 =  JSON.parseObject(JSON.toJSONString(serializeTest), SerializeTest.class);

        // 3.使用 Apache common 函数 SerializationUtils.clone()
        // SerializationUtils.clone() 函数核心逻辑就是 下面我自己写的 clone 方法。可以说是一模一样
//        SerializeTest serializeTest1 = SerializationUtils.clone(serializeTest);

        serializeTest1.setAge(0);
        serializeTest1.setName("nnnn");
        serializeTest1.getAddressTest().setProv("chongqing");
        System.out.println(serializeTest);
        System.out.println(serializeTest1);
    }

总结

Java 实现深拷贝的两种方式:

  • 实现 Cloneable 接口

在复写 clone() 方法的时候,对里面的对象都要重写,比较麻烦也容易忘记复写,导致出错

比如:cloneTest.setAddress((Address) address.clone());

  • 实现 Serializable 接口

比较常用,特别是 fastJson 。Apache 工具包也是经典。