Java的浅拷贝与深拷贝
Java中,Object基类有clone方法,但是如果要使用该方法,必须在要被Clone的类实现(implements)Cloneable接口,否则会报java.lang.CloneNotSupportedException异常,Cloneable接口是在java.lang中自动被导入的。
如果不重写clone()方法,则在调用clone()方法实现的是浅复制(所有的引用对象保持不变,意思是如果原型里这些对象发生改变会直接影响到复制对象)。重写clone()方法,一般会先调用super.clone()进行浅复制,然后再复制那些易变对象,从而达到深复制的效果。
我们来看一下代码:
public class Resume implements Cloneable {
private String name;
private String sex;
private String age;
private String timeArea;
private String company;
public Resume(String name) {
this.name = name;
}
//设置个人信息
public void setPersonInfo(String age, String sex) {
this.age = age;
this.sex = sex;
}
//设置工作经历
public void setWorkExperience(String timeArea, String company) {
this.timeArea = timeArea;
this.company = company;
}
//显示
public void display() {
System.out.println(name + " " + sex + " " + age);
System.out.println("工作经历:" + timeArea + " " + company);
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String args[]) throws CloneNotSupportedException {
Resume r = new Resume("大鸟");
r.setPersonInfo("28", "男");
r.setWorkExperience("1998-2000", "XX公司");
Resume r2 = (Resume)r.clone();
r2.setWorkExperience("2000-2003", "YY企业");
Resume r3 = (Resume)r.clone();
r3.setWorkExperience("2003-2020", "ZZ公司");
r.display();
r2.display();
r3.display();
}
}
结果是:
大鸟 男 28
工作经历:1998-2000 XX公司
大鸟 男 28
工作经历:2000-2003 YY企业
大鸟 男 28
工作经历:2003-2020 ZZ公司
super.clone(),这个操作主要是来做一次bitwise copy( binary copy ),即浅拷贝,他会把原对象完整的拷贝过来包括其中的引用。这样会带来问题,如果里面的某个属性是个可变对象,那么原来的对象改变,克隆的对象也跟着改变。所以在调用完super.clone()后,一般还需要重新拷贝可变对象。
但是如果你没有重写clone方法,则无法克隆Error: java: clone() 在 java.lang.Object 中是 protected 访问控制
总结:
每层clone()都顺着 super.clone() 的链向上调用的话最终就会来到Object.clone() ,于是根据上述的特殊语义就可以有 x.clone.getClass() == x.getClass() 。
至于如何实现的,可以把JVM原生实现的Object.clone()的语义想象成拿到this引用后通过反射去找到该对象实例的所有字段,然后逐一字段拷贝。
HotSpot vm中,Object.clone()在不同的优化层级上有不同的实现。在其中最不优化的版本是这样做的:拿到this引用,通过对象头里记录的Klass信息去找出这个对象有多大,然后直接分配一个新的同样大的空对象并且把Klass信息塞进对象头(这样就已经实现了x.clone.getClass() == x.getClass()这部分语义),然后直接把对象体 的内容看作数组拷贝一样从源对象“盲”拷贝到目标对象,bitwise copy。然后就完事啦。
我的理解是super.clone() 的调用就是沿着继承树不断网上递归调用直到Object 的clone方法,而跟据JavaDoc所说Object.clone()根据当前对象的类型创建一个新的同类型的空对象,然后把当前对象的字段的值逐个拷贝到新对象上,然后返回给上一层clone() 调用。
也就是说super.clone() 的浅复制效果是通过Object.clone()实现的。