铁锤的IT奇妙探险 Java Back-End Coder

带你了解深克隆和浅克隆

2020-07-03

JVM

简介:深克隆和浅克隆的区别,实现深克隆的三种方式。

深克隆和浅克隆

  • 浅克隆(Shadow Clone)是把原型对象中成员变量为值类型的属性都复制给克隆对象,把原型对象中成员变量为引用类型的引用地址也复制给克隆对象,也就是原型对象中如果有成员变量为引用对象,则此引用对象的地址是共享给原型对象和克隆对象的。
  • 深克隆(Deep Clone)是将原型对象中的所有类型,无论是值类型还是引用类型,都复制一份给克隆对象,也就是说深克隆会把原型对象和原型对象所引用的对象,都复制一份给克隆对象
public static void main(String[] args) throws CloneNotSupportedException {
        // 创建被赋值对象
        People p1 = new People();
        p1.setId(1);
        p1.setName("Java");
        // 克隆 p1 对象
        People p2 = (People) p1.clone();

        System.out.println("p2:" + p2.getName());
    }
static class People implements Cloneable{}
p2:Java

实现克隆则需要实现Cloneable接口,并重写Object类的clone()方法

Arrays.copyOf()

People[] o1 = {new People(1, "Java")};
People[] o2 = Arrays.copyOf(o1, o1.length);

o1[0].setName("Jdk");
System.out.println("o1:" + o1[0].getName());
System.out.println("o2:" + o2[0].getName());
o1:Jdk
o2:Jdk
  • 在修改克隆对象的第一个元素之后,原型对象的第一个元素也跟着被修改了,这说明 Arrays.copyOf() 其实是一个浅克隆,因为数组本身就是引用类型,在使用copyOf()时知识复制了引用地址给克隆对象。

深克隆实现

/**
 * 用户类
 */
public class People {
    private Integer id;
    private String name;
    private Address address; // 包含 Address 引用对象
    // 忽略构造方法、set、get 方法
}
/**
 * 地址类
 */
public class Address {
    private Integer id;
    private String city;
    // 忽略构造方法、set、get 方法
}

所有对象都实现克隆

//使People和Address类都实现Cloneable接口,从而实现People的深克隆
public class CloneExample {
    public static void main(String[] args) throws CloneNotSupportedException {
          // 创建被赋值对象
          Address address = new Address(110, "北京");
          People p1 = new People(1, "Java", address);
          // 克隆 p1 对象
          People p2 = p1.clone();
          // 修改原型对象
          p1.getAddress().setCity("西安");
          // 输出 p1 和 p2 地址信息
          System.out.println("p1:" + p1.getAddress().getCity() +
                  " p2:" + p2.getAddress().getCity());
    }
static class People implements Cloneable{}
static class Address implements Cloneable{}

p1:西安 p2:北京

字节流

public class ThirdExample {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 创建对象
        Address address = new Address(110, "北京");
        People p1 = new People(1, "Java", address);

        // 通过字节流实现克隆
        People p2 = (People) StreamClone.clone(p1);

    }

    /**
     * 通过字节流实现克隆
     */
    static class StreamClone {
        public static <T extends Serializable> T clone(People obj) {
            T cloneObj = null;
            try {
                // 新建字节数组输出流缓冲区
                ByteArrayOutputStream bo = new ByteArrayOutputStream();

				//生成序列化的关键类对象ObjectOutputStream
				//传入一个OutputStream参数表示将对象二进制流写入指定的OutputStream,这里即字节数组
                ObjectOutputStream oos = new ObjectOutputStream(bo);

				//将对象转换成二进制流
                oos.writeObject(obj);
                oos.close();

                // 分配内存,写入原始对象,生成新对象
                ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());//获取上面的输出字节流
                ObjectInputStream oi = new ObjectInputStream(bi);

                // 返回生成的新对象
                cloneObj = (T) oi.readObject();
                oi.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return cloneObj;
        }
    }
  • 先将要原型对象写入到内存中的字节流,然后再从这个字节流中读出刚刚存储的信息,来作为一个新的对象返回,那么这个新对象和原型对象就不存在任何地址上的共享,这样就实现了深克隆。
  • 每个对象必须能被序列化,必须实现Serializable接口,否则会抛出异常java.io.NotSerializableException

JSON工具类

import com.google.gson.Gson;

public class FifthExample {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 创建对象
        Address address = new Address(110, "北京");
        People p1 = new People(1, "Java", address);

        // 调用 Gson 克隆对象
        Gson gson = new Gson();
        People p2 = gson.fromJson(gson.toJson(p1), People.class);
}

使用Gson会先把对象转化成字符串,再从字符串转化成新的对象,因为新对象是从字符串转化而来的,因此不会和原型对象有任何的关联,这样就实现了深克隆。


Similar Posts

Comments