浅拷贝和深拷贝

首先需要明白,浅拷贝和深拷贝都是针对一个已有对象的操作。那先来看看浅拷贝和深拷贝的概念。

1、浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

Java 的深拷贝和浅拷贝

2、深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
Java 的深拷贝和浅拷贝
所以到现在,就应该了解了,所谓的浅拷贝和深拷贝,只是在拷贝对象的时候,对类的实例对象这种引用数据类型的不同操作而已。

Java 中的 clone()

在 Java 中,所有的 Class 都继承自 Object ,而在 Object 上,存在一个 clone() 方法,它被声明为了 protected ,所以我们可以在其子类中,使用它。

而无论是浅拷贝还是深拷贝,都需要实现 clone() 方法,来完成操作。

	protected Object clone() throws CloneNotSupportedException {
        if (!(this instanceof Cloneable)) {
            throw new CloneNotSupportedException("Class " + getClass().getName() +
                                                 " doesn't implement Cloneable");
        }

        return internalClone();
    }

    /*
     * Native helper method for cloning.
     */
    @FastNative
    private native Object internalClone();

可以看到,它的实现非常的简单,它限制所有调用 clone() 方法的对象,都必须实现 Cloneable 接口,否者将抛出 CloneNotSupportedException 这个异常。最终会调用 internalClone() 方法来完成具体的操作。而 internalClone() 方法,实则是一个 native 的方法。对此我们就没必要深究了,只需要知道它可以 clone() 一个对象得到一个新的对象实例即可。

public interface Cloneable {
}

而反观 Cloneable 接口,可以看到它其实什么方法都不需要实现。对他可以简单的理解只是一个标记,是开发者允许这个对象被拷贝。

浅拷贝例子

先来看看浅拷贝的例子。

首先创建一个 class 为 MainObj ,对其实现 Cloneable 接口,并且重写 clone() 方法。

public class MainObj implements Cloneable {

	 public MainObj() {
        System.out.println("MainObj构造函数,拷贝的时候不会调用");
    }

    private String name;
    private SubObj subObj;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public SubObj getSubObj() {
        return subObj;
    }

    public void setSubObj(SubObj subObj) {
        this.subObj = subObj;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        try {
            // 直接调用父类的clone()方法
            return super.clone();
        } catch (CloneNotSupportedException e) {
            return null;
        }
    }
}

和SubObj

public class SubObj implements Cloneable {
	
	public SubObj() {
        System.out.println("SubObj构造函数,拷贝的时候不会调用");
    }
    
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        try {
            // 直接调用父类的clone()方法
            return super.clone();
        } catch (CloneNotSupportedException e) {
            return null;
        }
    }

}

然后进行测试

public class Test {

    public static void main(String[] args) throws CloneNotSupportedException {


        String divider_line="--------------------------------------";

        // 原始对象
        SubObj subObj=new SubObj();
        subObj.setName("sub");

        MainObj sourceObj = new MainObj();
        sourceObj.setName("source");
        sourceObj.setSubObj(subObj);


        System.out.println("原始对象:" + sourceObj.getName() + " - " + sourceObj.getSubObj().getName());

        System.out.println(divider_line);

        // 拷贝对象
        MainObj copiedObj = (MainObj) sourceObj.clone();
        System.out.println("拷贝对象:" + copiedObj.getName() + " - " + copiedObj.getSubObj().getName());

        System.out.println(divider_line);

        // 原始对象和拷贝对象是否一样:
        System.out.println("原始对象和拷贝对象是否一样:" + (sourceObj == copiedObj));
        // 原始对象和拷贝对象的name属性是否一样
        System.out.println("原始对象和拷贝对象的name属性是否一样:" + (sourceObj.getName() == copiedObj.getName()));
        // 原始对象和拷贝对象的subj属性是否一样
        System.out.println("原始对象和拷贝对象的subj属性是否一样:" + (sourceObj.getSubObj() == copiedObj.getSubObj()));

        System.out.println(divider_line);

        sourceObj.setName("update");
        sourceObj.getSubObj().setName("update_sub");

        System.out.println("更新后的原始对象:" + sourceObj.getName() + " - " + sourceObj.getSubObj().getName());
        System.out.println("更新原始对象后的克隆对象t:" + copiedObj.getName() + " - " + copiedObj.getSubObj().getName());


    }

}

最后看看输出的 Log :

SubObj构造函数,拷贝的时候不会调用
MainObj构造函数,拷贝的时候不会调用
--------------------------------------
原始对象:source - sub
--------------------------------------
拷贝对象:source - sub
--------------------------------------
原始对象和拷贝对象是否一样:false
原始对象和拷贝对象的name属性是否一样:true
原始对象和拷贝对象的subj属性是否一样:true
--------------------------------------
更新后的原始对象:update - update_sub
更新原始对象后的克隆对象t:source - update_sub

从输出结果中我们可以看到,对原始对象sourceObj的"name"属性所做的改变并没有影响到拷贝对象copiedObj,但是对引用对象subObj的"name"属性所做的改变影响到了拷贝对象copiedObj。

深拷贝例子

比较常用的方案有两种:

  • 序列化(serialization)这个对象,再反序列化回来,就可以得到这个新的对象,无非就是序列化的规则需要我们自己来写。
  • 继续利用 clone() 方法,既然 clone() 方法,是我们来重写的,实际上我们可以对其内的引用类型的变量,再进行一次 clone()。
利用clone实现

接连上文,利用clone()方法只要在浅拷贝的例子上对MainObj做一点小改动,就可以实现深拷贝SubObj ,其他类都没有变化。

public class MainObj implements Cloneable {

    private String name;
    private SubObj subObj;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public SubObj getSubObj() {
        return subObj;
    }

    public void setSubObj(SubObj subObj) {
        this.subObj = subObj;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        try {

            //浅拷贝
            // 直接调用父类的clone()方法
            //  return super.clone();

            //深拷贝
            MainObj m=(MainObj) super.clone();
            m.subObj=(SubObj)this.subObj.clone();
            return  m;

        } catch (CloneNotSupportedException e) {
            return null;
        }
    }
}

最重要的代码就在 MainObj.clone() 中,它对其内的 subObj ,再进行了一次 clone() 操作。

再来看看输出的 Log。

SubObj构造函数,拷贝的时候不会调用
MainObj构造函数,拷贝的时候不会调用
--------------------------------------
原始对象:source - sub
--------------------------------------
拷贝对象:source - sub
--------------------------------------
原始对象和拷贝对象是否一样:false
原始对象和拷贝对象的name属性是否一样:true
原始对象和拷贝对象的subj属性是否一样:false
--------------------------------------
更新后的原始对象:update - update_sub
更新原始对象后的克隆对象t:source - sub

实则浅拷贝和深拷贝只是相对的,如果一个对象内部只有基本数据类型,那用 clone() 方法获取到的就是这个对象的深拷贝,而如果其内部还有引用数据类型,那用 clone() 方法就是一次浅拷贝的操作。

序列化实现

把对象写到流里的过程是序列化(Serilization)过程;而把对象从流中读出来的反序列化(Deserialization)过程。应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。
在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里,再从流里读出来,便可以重建对象。

变动的代码

    public class MainObj implements Cloneable, Serializable{
        private static final long serialVersionUID = 2631590509760908280L;
        ..................
    }

    public class Email implements Cloneable, Serializable{
        private static final long serialVersionUID = 1267293988171991494L;
        ....................
    }
    public class Test {

    	public static void main(String[] args) throws CloneNotSupportedException {
    		..................
    		// 拷贝对象
    		//MainObj copiedObj = (MainObj) sourceObj.clone();
			MainObj copiedObj = DeepClone.clone(sourceObj);
			..................
		}
	}

工具类 DeepClone

import java.io.*;

public class DeepClone {

    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T clone(T obj) {
        T cloneObj = 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);
            //返回生成的新对象
            cloneObj = (T) ois.readObject();
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cloneObj;
    }

}
SubObj构造函数,拷贝的时候不会调用
MainObj构造函数,拷贝的时候不会调用
--------------------------------------
原始对象:source - sub
--------------------------------------
拷贝对象:source - sub
--------------------------------------
原始对象和拷贝对象是否一样:false
原始对象和拷贝对象的name属性是否一样:false
原始对象和拷贝对象的subj属性是否一样:false
--------------------------------------
更新后的原始对象:update - update_sub
更新原始对象后的克隆对象t:source - sub

注意这个name属性的地址也和原先不一样了,但是值相等。可以添加一个在MainObj中添加一个基本数据类型的int age做对比。

源码地址 https://github.com/alinainai/test_11/blob/master/src/designpattern/prototype/Test.java

参考:

https://juejin.im/post/5ab75dde5188255579189f2d
https://www.cnblogs.com/plokmju/p/7357205.html
https://www.jianshu.com/p/8c74edbb46c0

相关文章: