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. 从远程库github.com克隆代码时遇到了如下的问题:

    Warning: Permanently added the RSA host key for IP address '13.250.177.223' to the list of known hos ...

  2. API可视化管理平台YApi

    Yapi是什么 YApi 是高效.易用.功能强大的 api 管理平台,旨在为开发.产品.测试人员提供更优雅的接口管理服务.可以帮助开发者轻松创建.发布.维护 API,YApi 还为用户提供了优秀的交互 ...

  3. ubuntu20 使用命令安装 rabbitmq

    安装 rabbitmq sudo apt-get install erlang-nox -y sudo apt-get update sudo apt-get install rabbitmq-ser ...

  4. thinkphp6.0.x 反序列化详记(二)

    前言 接上文找第二条POP链. 环境配置 同上文 POP链构造 寻找__destruct方法 仍然是寻找__destruct,这次关注AbstractCache.php(/vendor/league/ ...

  5. operator bool()是什么

    operator bool()是什么 在C++中,operator TypeName()语法用来将对象转换为指定的TypeName类型,当这里TypeName为bool时,就可以直接在条件判断式里面直 ...

  6. CentOS7克隆多个虚拟机

    VMware+centos7克隆虚拟机 步骤一:打开虚拟机,右键选中已经配置好的虚拟机,选择manage下面的clone选项.这里有一个需要注意的地方,就是虚拟机在启动或者挂起的状态下是不能clone ...

  7. ansible:安装nginx1.18.0(使用role功能)

    一,ansible使用role的用途? roles分别将变量/文件/任务/模板/handler等放置于单独的目录中, 并可以方便的include各目录下的功能 roles使playbook能实现代码被 ...

  8. vue学习大纲

    第一单元  vue基础 第二单元  VueU学习

  9. 通过代码实现 `OutOfMemory

    通过代码实现 OutOfMemory Intro 来尝试写一个发生 OutOfMemoryException 的代码吧,开启煞笔代码第三篇 -- OutofMemory OutOfMemory Out ...

  10. 循序渐进VUE+Element 前端应用开发(21)--- 省市区县联动处理的组件使用

    在很多应用中,往往都涉及到记录用户所在省份.城市.区县或者街道等信息,一般我们可以通过联动的Select或者类似的界面组件进行展示,或者使用Element中的el-cascader界面组件进行展示,而 ...