Java的浅拷贝与深拷贝

Java中,所有的类都继承Object,Object中有clone方法,它被声明为了 protected ,所以我们但是如果要使用该方法就得重写且声明为public必须在要被Clone的类实现(implements)Cloneable接口,否则会报java.lang.CloneNotSupportedException异常,Cloneable接口是在java.lang中自动被导入的,而无论是浅拷贝还是深拷贝,都需要实现 clone() 方法。

如果不重写clone()方法,则在调用clone()方法实现的是浅复制(所有的引用对象保持不变,意思是如果原型里这些对象发生改变会直接影响到复制对象)。重写clone()方法,一般会先调用super.clone()进行浅复制,然后再复制那些易变对象,从而达到深复制的效果。

浅拷贝

一般的话,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公司

将WorkExperience封装为一个类,看看有什么不同:

public class Resume implements Cloneable {

    private String name;
private String sex;
private String age;
private WorkExperience we = null; public Resume(String name) {
this.name = name;
we = new WorkExperience();
} //设置个人信息
public void setPersonInfo(String age, String sex) {
this.age = age;
this.sex = sex;
} public void setWorkExperience(String timeArea, String company) {
we.setTimeArea(timeArea);
we.setCompany(company);
} //显示
public void display() {
System.out.println(name + " " + sex + " " + age);
System.out.println("工作经历:" + we.getTimeArea() + " " + we.getCompany());
} @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.setPersonInfo("31", "男");
r2.setWorkExperience("2000-2003", "YY企业");
Resume r3 = (Resume)r.clone();
r3.setWorkExperience("2003-2020", "ZZ公司");
r.display();
r2.display();
r3.display();
}
} class WorkExperience {
private String timeArea;
private String company; public String getTimeArea() {
return timeArea;
} public void setTimeArea(String timeArea) {
this.timeArea = timeArea;
} public String getCompany() {
return company;
} public void setCompany(String company) {
this.company = company;
}
}

这时结果的输出是:

大鸟 男 28
工作经历:2003-2020 ZZ公司
大鸟 男 31
工作经历:2003-2020 ZZ公司
大鸟 男 28
工作经历:2003-2020 ZZ公司

可以看出,这样子会导致如果被克隆的类中还存在其他的类的话,就只会将这个引用指向那个对象,实际上没有克隆

super.clone(),这个操作主要是来做一次bitwise copy( binary copy ),即浅拷贝,他会把原对象完整的拷贝过来包括其中的引用。这样会带来问题,如果里面的某个属性是个可变对象,那么原来的对象改变,克隆的对象也跟着改变。所以在调用完super.clone()后,一般还需要重新拷贝可变对象。

但是如果你没有重写clone方法,则无法克隆Error: java: clone() 在 java.lang.Object 中是 protected 访问控制


深拷贝:

那么,如何进行一个深拷贝呢?

比较常用的方案有两种:

  • 序列化(serialization)这个对象,再反序列化回来,就可以得到这个新的对象,无非就是序列化的规则需要我们自己来写。

  • 继续利用 clone() 方法,既然 clone() 方法,是我们来重写的,实际上我们可以对其内的引用类型的变量,再进行一次 clone()。

    继续改写上面的 Demo ,让 ChildClass 也实现 Cloneable 接口。

    需要将代码改为如下即可:

    public class Resume implements Cloneable {
    
        @Override
    protected Object clone() throws CloneNotSupportedException {
    Resume r = (Resume)super.clone();
    r.we = (WorkExperience)this.we.clone();
    return r;
    }
    } class WorkExperience implements Cloneable { @Override
    protected Object clone() throws CloneNotSupportedException {
    return super.clone();
    }
    }

    重写两个方法的clone后,结果就可以进行深拷贝了:

    大鸟 男 28
    工作经历:1998-2000 XX公司
    大鸟 男 31
    工作经历:2000-2003 YY企业
    大鸟 男 28
    工作经历:2003-2020 ZZ公司

总结:

每层clone()都顺着 super.clone() 的链向上调用的话最终就会来到Object.clone() ,于是根据上述的特殊语义就可以有 x.clone.getClass() == x.getClass() 。

至于如何实现的,可以把JVM原生实现的Object.clone()的语义想象成拿到this引用后通过反射去找到该对象实例的所有字段,然后逐一字段拷贝。

HotSpot vm中,Object.clone()在不同的优化层级上有不同的实现。在其中最不优化的版本是这样做的:拿到this引用,通过对象头里记录的Class信息去找出这个对象有多大,然后直接分配一个新的同样大的空对象并且把Class信息塞进对象头(这样就已经实现了x.clone.getClass() == x.getClass()这部分语义),然后直接把对象体 的内容看作数组拷贝一样从源对象“盲”拷贝到目标对象,bitwise copy。

我的理解是super.clone() 的调用就是沿着继承树不断网上递归调用直到Object 的clone方法,而跟据JavaDoc所说Object.clone()根据当前对象的类型创建一个新的同类型的空对象,然后把当前对象的字段的值逐个拷贝到新对象上,然后返回给上一层clone() 调用。

也就是说super.clone() 的浅复制效果是通过Object.clone()实现的。

Java的浅拷贝与深拷贝的更多相关文章

  1. 浅析java的浅拷贝和深拷贝

    Java中任何实现了Cloneable接口的类都可以通过调用clone()方法来复制一份自身然后传给调用者.一般而言,clone()方法满足:       (1) 对任何的对象x,都有x.clone( ...

  2. Java的浅拷贝与深拷贝总结

    Java中的对象拷贝(Object Copy)指的是将一个对象的所有属性(成员变量)拷贝到另一个有着相同类类型的对象中去.举例说明:比如,对象A和对象B都属于类S,具有属性a和b.那么对对象A进行拷贝 ...

  3. 渐析java的浅拷贝和深拷贝

          首先来看看浅拷贝和深拷贝的定义:       浅拷贝:使用一个已知实例对新创建实例的成员变量逐个赋值,这个方式被称为浅拷贝.       深拷贝:当一个类的拷贝构造方法,不仅要复制对象的所 ...

  4. Java中浅拷贝和深拷贝的区别

    浅拷贝和深拷贝的定义: 浅拷贝: 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.即对象的浅拷贝会对"主"对象进行拷贝,但不会复制主对象 ...

  5. 初始JAVA中浅拷贝和深拷贝

    1. 简单变量的复制 public static void main(String[] args) { int a = 5; int b = a; System.out.println(a); Sys ...

  6. Java之浅拷贝与深拷贝

    ----?浅拷贝 --- 概念 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.简单说,浅拷贝就是只复制所考虑的对象,而不复制它所引用的对象 --- 实现方 ...

  7. Java之浅拷贝和深拷贝

    [概述] Java中的对象拷贝 ( Object Copy ) 是指将一个对象的所有属性(成员变量)拷贝到另一个有着相同类类型的对象中去.例如,对象 A 和对象 B 都属于类 S,具有属性 a 和 b ...

  8. java的浅拷贝和深拷贝(待解决)

    1.什么是浅拷贝,什么是深拷贝? 2.storm的并行度问题,需要使用全局变量static ConcorrentHashMap,因为加了static,所有的线程只能拷贝该全局变量的一个唯一的副本,进行 ...

  9. 【转】JAVA中的浅拷贝和深拷贝

    原文网址:http://blog.bd17kaka.net/blog/2013/06/25/java-deep-copy/ JAVA中的浅拷贝和深拷贝(shallow copy and deep co ...

随机推荐

  1. 安装zabbix3.0以及升级到5.0过程

    关闭防火墙: systemctl stop firewalld.service systemctl disable firewalld.service 需要关闭 selinux,一定要关闭这个,开启s ...

  2. Office远程代码执行漏洞(CVE-2017-11882)

    POC: https://github.com/Ridter/CVE-2017-11882/ 一.简单的生成弹计算器的doc文件. 网上看到的改进过的POC,我们直接拿来用,命令如下: #python ...

  3. Zookeeper基础理论

    Zookeeper是分布式开源协调服务, 主要用来解决分布式集群中应用系统的一致性问题. 本质上是分布式小文件存储系统.   特性 全局数据一致性(集群中每个服务器保存一份相同的数据副本,Client ...

  4. 几个常用markdown工具的主要优缺点

    几个常用markdown工具的主要优缺点 最近对几个热门的markdown工具做了一个对比表 表格 脚注 图片和图床 平台 移动端 实时预览 收费 操作难度 导出功能 mweb 非常棒 预览正常显示 ...

  5. 微服务 | Spring Cloud(一):从单体SSM 到 Spring Cloud

    系列文章目录 微服务 | Spring Cloud(一):从单体SSM 到 Spring Cloud 目录 系列文章目录 前言 单体式架构 微服务架构 优点 缺点 服务发现与弹性扩展 参考 前言 在微 ...

  6. 53.Qt-QPdfWriter绘制PDF,支持表单输出

    之前打印PDF都是通过html形式来实现的,但是这次要做的东西,需要打印界面控件,所以需要使用QPdfWriter. 通过QPdfWriter来获取QPainter对象,就能实现在PDF上来画画啦. ...

  7. 6.Android-五大布局

    Android 五大布局如下所示: LinearLayout 线性布局 只能指定一个方向(垂直/水平)来布局 RelativeLayout 相对布局 通过某个控件为参照物,来定位其它控件的位置的布局方 ...

  8. 单调队列优化题:最大数(P1198)

    题目描述 现在请求你维护一个数列,要求提供以下两种操作: 1. 查询操作. 语法:Q L 功能:查询当前数列中末尾L个数中的最大的数,并输出这个数的值. 限制:不超过当前数列的长度.(L>0) ...

  9. mac 解决安卓模拟器链接不上网络

    方法1.临时方法,每次启动都要加114.114.114.114 1.进入到下面的目录 /Users/anxiaodong/Library/Android/sdk/emulator 2.执行以下命令 e ...

  10. Python基础数据类型方法补充

    str 补充的方法: capitalize():首字母大写,其余变小写 s = 'liBAI' s1 = s.capitalize() print(s1) # Libai swapcase():大小写 ...