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 ...
随机推荐
- LVM的简单使用及常用的命令总结
Lvm的简单使用及常用的命令总结 centos7中默认使用的是xfs文件系统,此文件系统的特点,可以另外查找资料,这里说一下对文件系统的扩容: 1.先看一下没扩容之前的分区大小 2.添加一块新磁盘,并 ...
- C#编写一个较完整的记事本程序
开发环境 Visual Studio 2019 至少需安装 .NET桌面开发 创建项目并配置 创建窗体文件 配置项目名称及框架 设计界面 创建窗体文件,将控件摆放位置如下,参考系统自带的记事本程序 窗 ...
- C++中try&catch
参考: https://blog.csdn.net/xueluowutong/article/details/81257654 在c++中,可以直接抛出异常之后自己进行捕捉处理,如:(这样就可以在 ...
- 如何实现文章AI伪原创?
language-ai 文章AI伪原创,文章自动生成,NLP,自然语言技术处理,DNN语言模型,词义相似度分析.全网首个AI伪原创开源应用类项目. 点击右侧about内的链接极速体验! 代码托管在gi ...
- 《Android逆向反编译代码注入》 - 逆向安全入门必看视频教程
适合人群: Android开发人员.逆向反编译开发人员.以及对Android逆向安全感兴趣的朋友. 视频地址: 51CTO学院:https://edu.51cto.com/course/24485 ...
- mycat的privileges标签
参考https://blog.csdn.net/tornadojava/article/details/54948662 privileges标签 对用户的 schema以及表进行精细化的DML权限控 ...
- 多测师讲解自动化--rf断言(下)--_高级讲师肖sir
1.Page Should Contain 1.1存在页面上的内容 1.2 不存在页面上的内容, 运行抛异常 1.3 Page Should Not Contain 1.4 1.5 输入页面元素,判断 ...
- abp(net core)+easyui+efcore实现仓储管理系统——出库管理之三(五十一)
abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统--ABP总体介绍(一) abp(net core)+ ...
- MySQL安装版本介绍
MySQL安装 源码安装 二进制格式的程序包 程序包管理器安装 yum dnf MySQL在CentOS上的情况 6上只有mySQL 7上只有mariadb 8上既有mysql也有mariadb Ce ...
- Vue踩坑日记-Element this.$message 找不到模块
在使用Vue.js的 Element框架时,无法使用Message组件 浏览器提示:找不到组件 原始写法: this.$message({ message: '恭喜你,这是一条成功消息', type: ...