Java的浅拷贝与深拷贝
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的浅拷贝与深拷贝的更多相关文章
- 浅析java的浅拷贝和深拷贝
Java中任何实现了Cloneable接口的类都可以通过调用clone()方法来复制一份自身然后传给调用者.一般而言,clone()方法满足: (1) 对任何的对象x,都有x.clone( ...
- Java的浅拷贝与深拷贝总结
Java中的对象拷贝(Object Copy)指的是将一个对象的所有属性(成员变量)拷贝到另一个有着相同类类型的对象中去.举例说明:比如,对象A和对象B都属于类S,具有属性a和b.那么对对象A进行拷贝 ...
- 渐析java的浅拷贝和深拷贝
首先来看看浅拷贝和深拷贝的定义: 浅拷贝:使用一个已知实例对新创建实例的成员变量逐个赋值,这个方式被称为浅拷贝. 深拷贝:当一个类的拷贝构造方法,不仅要复制对象的所 ...
- Java中浅拷贝和深拷贝的区别
浅拷贝和深拷贝的定义: 浅拷贝: 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.即对象的浅拷贝会对"主"对象进行拷贝,但不会复制主对象 ...
- 初始JAVA中浅拷贝和深拷贝
1. 简单变量的复制 public static void main(String[] args) { int a = 5; int b = a; System.out.println(a); Sys ...
- Java之浅拷贝与深拷贝
----?浅拷贝 --- 概念 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.简单说,浅拷贝就是只复制所考虑的对象,而不复制它所引用的对象 --- 实现方 ...
- Java之浅拷贝和深拷贝
[概述] Java中的对象拷贝 ( Object Copy ) 是指将一个对象的所有属性(成员变量)拷贝到另一个有着相同类类型的对象中去.例如,对象 A 和对象 B 都属于类 S,具有属性 a 和 b ...
- java的浅拷贝和深拷贝(待解决)
1.什么是浅拷贝,什么是深拷贝? 2.storm的并行度问题,需要使用全局变量static ConcorrentHashMap,因为加了static,所有的线程只能拷贝该全局变量的一个唯一的副本,进行 ...
- 【转】JAVA中的浅拷贝和深拷贝
原文网址:http://blog.bd17kaka.net/blog/2013/06/25/java-deep-copy/ JAVA中的浅拷贝和深拷贝(shallow copy and deep co ...
随机推荐
- Web开发初探之JavaScript 快速入门
本文改编和学习自 A JavaScript Primer For Meteor 和 MDN Web教程 前文 Web开发初探 概述 本文以介绍 JavaScript 为主,初学者掌握本文的内容后,将能 ...
- Java知识系统回顾整理01基础03变量07final关键字
一.final赋值 final 修饰一个变量,有很多种说法,比如不能改变等等 准确的描述是 当一个变量被final修饰的时候,该变量只有一次赋值的机会 二.在声明的时候赋值 i已经被赋值为5,所以这里 ...
- 【漏洞复现】PHPmyadmin 4.8.1后台Getshell新姿势
原文地址:https://mp.weixin.qq.com/s/HZcS2HdUtqz10jUEN57aog 早上看到群里在讨论一个新姿势,phpmyadmin后台getshell,不同于以往需要知道 ...
- Beyond Compare 3, 简体中文版 安装
转载: 1.https://www.scootersoftware.com/download.php 2.http://www.scootersoftware.com/download.php 下载地 ...
- C/C++ 条件编译
条件编译就是指有条件的编译,即根据条件去编译代码,在编译阶段时就对代码做出取舍,有的编译,有的不编译,这样比写成一个个判断函数更有效率,比如工程代码大部分的地方都类似,只有个别语句因为使用的硬件版本不 ...
- 《C++primerplus》第12章练习题
做一下倒数两题,都是在队列模拟的程序基础上做点修改测试. 5.找出平均等候时间为1分钟时,每小时到达的客户数为多少(试验时间不少于100小时). 指定队伍最大长度10人,模拟100小时.粗略估计答案在 ...
- 如何获取value值,获取属性值,设置属性值,
1.获取select下拉框的value值, 2.获取select 的tid值 3.设置属性值 4.判断哪个单选框选中了 prop好像是判断的意思吧,个人理解勿喷谢谢!!!!!!
- docker-管理镜像常用命令
1. docker管理镜像常用命令 指令 说明 docker run [容器 CONTAINER ID] 创建一个新的容器并运行一个命令 docker start [容器 CONT ...
- Python+Appium自动化测试(15)-使用Android模拟器(详细)
做APP的UI自动化测试时,我们往往会使用真机跑自动化测试脚本,因为这样才是最真实的使用场景.但前期调试脚本的话,可以先使用模拟器,这样相对更加方便. 不推荐使用Android SDK里自带模拟器,太 ...
- 多测师讲解 ———python2和Python3区别
python3.x和python2.x的区别:1.Python3.X源码文件默认使用utf-8编码,而python2.x的编译最前端需要加上#coding=utf-82.python3.x里打印pri ...