Object.clone()方法与对象的深浅拷贝
转载:【https://www.cnblogs.com/nickhan/p/8569329.html】
引言
在某些场景中,我们需要获取到一个对象的拷贝用于某些处理。这时候就可以用到Java中的Object.clone方法进行对象复制,得到一个一模一样的新对象。但是在实际使用过程中会发现:当对象中含有可变的引用类型属性时,在复制得到的新对象对该引用类型属性内容进行修改,原始对象响应的属性内容也会发生变化,这就是"浅拷贝"的现象。关于浅拷贝,Object.clone()方法的描述也有说明:
一、浅拷贝
接下来用代码看看浅拷贝的效果、创建两个类ClassA、ClassB
public class ClassA implements Cloneable {
private String classNameA ;
private Integer ageA;
private ClassB classB;
public ClassA(Integer age, String classNameA, ClassB classB){
this.classNameA = classNameA;
this.ageA = age;
this.classB = classB;
}
public String getClassNameA() {
return classNameA;
}
public void setClassNameA(String classNameA) {
this.classNameA = classNameA;
}
public ClassB getClassB() {
return classB;
}
public void setClassB(ClassB classB) {
this.classB = classB;
}
public Integer getAge() {
return ageA;
}
public void setAge(Integer ageA) {
this.ageA = ageA;
}
protected Object clone() {
try{
return super.clone();
}catch(Exception e){
return null;
}
}
}
public class ClassB {
private String classNameB;
private Integer ageB;
public ClassB(Integer ageB, String classNameB){
this.ageB = ageB;
this.classNameB = classNameB;
}
public String getClassNameB() {
return classNameB;
}
public void setClassNameB(String classNameB) {
this.classNameB = classNameB;
}
public Integer getAgeB() {
return ageB;
}
public void setAgeB(Integer ageB) {
this.ageB = ageB;
}
}
测试代码:
public class Test {
public static void main(String[] args) throws IOException {
ClassA classA1 = new ClassA(22,"逝清雪", new ClassB(24,"莫问"));
ClassA classA2 = (ClassA) classA1.clone();
System.out.println(classA1 == classA2);
System.out.println("classA1" + "-----" + classA1.getAge() + "-----" + classA1.getClassNameA());
System.out.println("classA2" + "-----" + classA2.getAge() + "-----" + classA2.getClassNameA());
System.out.println("-------------------------------------------------------------------");
//改变克隆对象的基本类型变量
classA2.setAge(100);
classA2.setClassNameA("凌雪伤");
System.out.println("classA1" + "-----" + classA1.getAge() + "-----" + classA1.getClassNameA());
System.out.println("classA2" + "-----" + classA2.getAge() + "-----" + classA2.getClassNameA());
System.out.println("-------------------------------------------------------------------");
//改变克隆对象的引用对象的值
classA2.getClassB().setClassNameB("天下无双");
classA2.getClassB().setAgeB(120);
System.out.println("classA1" + "-----" + classA1.getClassB().getAgeB() + "-----" + classA1.getClassB().getClassNameB());
System.out.println("classA2" + "-----" + classA2.getClassB().getAgeB() + "-----" + classA2.getClassB().getClassNameB());
}
}
测试结果:
false
classA1-----22-----逝清雪
classA2-----22-----逝清雪
-------------------------------------------------------------------
classA1-----22-----逝清雪
classA2-----100-----凌雪伤
-------------------------------------------------------------------
classA1-----120-----天下无双
classA2-----120-----天下无双
可以看到打印结果:
第1句输出说明了原对象与新对象是两个不同的对象:
第2、3句可以看到拷贝出来的新对象与原对象内容一致:
第5、6句重新设置了拷贝对象的值,答应结果也和预期一样正确输出:
第8、9句重新设置了拷贝对象中引用变量的值,然而打印结果中原对象中的信息也跟着变化了,仔细观察发现原对象跟着变化的只是classB部分,这就跟clone本身的浅拷贝有关系了。
浅拷贝:创建一个新对象,然后将当前对象的非静态字段复制到该对象,如果字段类型是值类型(基本类型)的,那么对该字段进行复制;如果字段是引用类型的,则只复制该字段的引用而不复制引用指向的对象。此时新对象里面的引用类型字段相当于是原始对象里面引用类型字段的一个副本,原始对象与新对象里面的引用字段指向的是同一个对象,因此,修改classA2里面的ClassB内容时,原classA1里面的ClassB内容会跟着改变。
了解了浅拷贝,那么深拷贝是什么也就很清楚了。即将引用类型的属性内容也拷贝一份新的。那么,实现深拷贝我这里收集到两种方式:第一种是给需要拷贝的引用类型也实现Cloneable接口并覆写clone方法;第二种则是利用序列化。接下来分别对两种方式进行演示。
二、深拷贝:
1、深拷贝-clone方式:
对于以上演示代码,利用clone方式进行深拷贝无非就是将ClassB类也实现Cloneable,然后对ClassA的clone方法进行调整,下面对ClassA、ClassB做一下调整:
public class ClassA implements Cloneable {
private String classNameA ;
private Integer ageA;
private ClassB classB;
public ClassA(Integer age, String classNameA, ClassB classB){
this.classNameA = classNameA;
this.ageA = age;
this.classB = classB;
}
public String getClassNameA() {
return classNameA;
}
public void setClassNameA(String classNameA) {
this.classNameA = classNameA;
}
public ClassB getClassB() {
return classB;
}
public void setClassB(ClassB classB) {
this.classB = classB;
}
public Integer getAge() {
return ageA;
}
public void setAge(Integer ageA) {
this.ageA = ageA;
}
protected Object clone() {
try{
ClassA classA = (ClassA)super.clone();
//手动对address属性进行clone,并赋值给新的person对象
classA.classB = (ClassB)classB.clone();
return super.clone();
}catch(Exception e){
return null;
}
}
}
public class ClassB implements Cloneable{
private String classNameB;
private Integer ageB;
public ClassB(Integer ageB, String classNameB){
this.ageB = ageB;
this.classNameB = classNameB;
}
public String getClassNameB() {
return classNameB;
}
public void setClassNameB(String classNameB) {
this.classNameB = classNameB;
}
public Integer getAgeB() {
return ageB;
}
public void setAgeB(Integer ageB) {
this.ageB = ageB;
}
protected Object clone() {
try{
return super.clone();
}catch(Exception e){
return null;
}
}
}
测试结果:
false
classA1-----22-----逝清雪
classA2-----22-----逝清雪
-------------------------------------------------------------------
classA1-----22-----逝清雪
classA2-----100-----凌雪伤
-------------------------------------------------------------------
classA1-----24-----莫问
classA2-----120-----天下无双
可以看到原ClassA1对象的ClassB内容未受到影响。
但是,这种方法的缺点就是当一个类里面有很多引用类型时,需要手动调用很多clone,而且如果引用类型内部还有引用类型时,那么代码将会很恶心,量也很大。。。
所以这种方式一般用于引用类型变量较少的时候对于很多引用类型,可以使用序列化对象的方式进行深拷贝。
2、深拷贝-序列化方式
这种方式其实就是将对象转成二进制流,然后再把二进制流反序列成一个java对象,这时候反序列化生成的对象是一个全新的对象,里面的信息与原对象一样,但是所有内容都是一份新的,这种方式需要注意的地方主要是所有类都需要实现Serializable接口,以便进行序列化操作。
public class DeepClone implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 利用序列化和反序列化进行对象的深拷贝
* @return
* @throws Exception
*/
protected Object deepClone() throws Exception{
//序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
}
public class ClassA extends DeepClone {
private static final long serialVersionUID = 1L;
private String classNameA ;
private Integer ageA;
private ClassB classB;
public ClassA(Integer age, String classNameA, ClassB classB){
this.classNameA = classNameA;
this.ageA = age;
this.classB = classB;
}
public String getClassNameA() {
return classNameA;
}
public void setClassNameA(String classNameA) {
this.classNameA = classNameA;
}
public ClassB getClassB() {
return classB;
}
public void setClassB(ClassB classB) {
this.classB = classB;
}
public Integer getAge() {
return ageA;
}
public void setAge(Integer ageA) {
this.ageA = ageA;
}
}
public class ClassB extends DeepClone{
private static final long serialVersionUID = 1L;
private String classNameB;
private Integer ageB;
public ClassB(Integer ageB, String classNameB){
this.ageB = ageB;
this.classNameB = classNameB;
}
public String getClassNameB() {
return classNameB;
}
public void setClassNameB(String classNameB) {
this.classNameB = classNameB;
}
public Integer getAgeB() {
return ageB;
}
public void setAgeB(Integer ageB) {
this.ageB = ageB;
}
}
测试代码:
public class Test {
public static void main(String[] args) throws Exception {
ClassA classA1 = new ClassA(22,"逝清雪", new ClassB(24,"莫问"));
ClassA classA2 = (ClassA)classA1.deepClone();
System.out.println(classA1 == classA2);
System.out.println("classA1" + "-----" + classA1.getAge() + "-----" + classA1.getClassNameA());
System.out.println("classA2" + "-----" + classA2.getAge() + "-----" + classA2.getClassNameA());
System.out.println("-------------------------------------------------------------------");
//改变克隆对象的基本类型变量
classA2.setAge(100);
classA2.setClassNameA("凌雪伤");
System.out.println("classA1" + "-----" + classA1.getAge() + "-----" + classA1.getClassNameA());
System.out.println("classA2" + "-----" + classA2.getAge() + "-----" + classA2.getClassNameA());
System.out.println("-------------------------------------------------------------------");
//改变克隆对象的引用对象的值
classA2.getClassB().setClassNameB("天下无双");
classA2.getClassB().setAgeB(120);
System.out.println("classA1" + "-----" + classA1.getClassB().getAgeB() + "-----" + classA1.getClassB().getClassNameB());
System.out.println("classA2" + "-----" + classA2.getClassB().getAgeB() + "-----" + classA2.getClassB().getClassNameB());
}
}
测试结果:
false
classA1-----22-----逝清雪
classA2-----22-----逝清雪
-------------------------------------------------------------------
classA1-----22-----逝清雪
classA2-----100-----凌雪伤
-------------------------------------------------------------------
classA1-----24-----莫问
classA2-----120-----天下无双 Process finished with exit code 0
可见,对新对象用序列化的方式也可以实现深拷贝,这便是由Object.clone()引出的深拷贝与浅拷贝,学习记录一下。
Object.clone()方法与对象的深浅拷贝的更多相关文章
- 关于Java的Object.clone()方法与对象的深浅拷贝
文章同步更新在个人博客:关于Java的Object.clone()方法与对象的深浅拷贝 引言 在某些场景中,我们需要获取到一个对象的拷贝用于某些处理.这时候就可以用到Java中的Object.clon ...
- 总结JavaScript对象的深浅拷贝
十四.对象的浅拷贝与深拷贝 什么是对象的拷贝? 将一个对象赋值给另外一个对象, 我们称之为对象的拷贝 什么是深拷贝, 什么是浅拷贝? 我们假设将A对象赋值给B对象 浅拷贝是指, 修改B对象的属性和方法 ...
- Java clone() 方法克隆对象——深拷贝与浅拷贝
基本数据类型引用数据类型特点 1.基本数据类型的特点:直接存储在栈(stack)中的数据 2.引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里 引用数据类型在栈中存储了指针,该指 ...
- str中的join方法,fromkeys(),set集合,深浅拷贝(重点)
一丶对之前的知识点进行补充 1.str中的join方法.把列表转换成字符串 # 将列表转换成字符串,每个元素之间用_拼接 s = "_".join(["天",& ...
- Java 中如何使用clone()方法克隆对象?
java为什么要 对象克隆: 在程序开发时,有时可能会遇到以下情况:已经存在一个对象A,现在需要一个与A对象完全相同的B 对象,并对B 对象的属性值进行修改,但是A 对象原有的属性值不能改变.这时,如 ...
- Javascript 对象复制(深浅拷贝)
一.数据类型分类: 基本变量 引用类型 二.什么叫做指针指向 栈内存.堆内存.指针指向(如下红圈圈的斜线). 三.赋值.拷贝.引用区别? 赋值指一个变量赋予某个值,包含两种方式,一种是直接量,另一种, ...
- js对象的深浅拷贝
JS数据类型可以分为(ES5,暂时不考虑ES6): 简单数据类型:Number.String.undefined.boolean 复杂数据类型:Object.Array 简单的数据类型,往往是赋值操作 ...
- Object.keys()方法 返回对象属性数组
MDN语法 Object.keys(obj) 参数obj:要返回其枚举自身属性的对象. 返回值:一个表示给定对象的所有可枚举属性的字符串数组. 1.传入一个对象,返回的的是所有属性值 var obj2 ...
- 在vue项目中遇到关于对象的深浅拷贝问题
一.问题 项目里新添加了一个多选的功能,其显示的数据都是从后端返回过来的,我们需要在返回来的数据外再额外添加一个是否选中的标记,我的选择是在返回正确的数据时将标记添加进去,然后push到数组中.然后就 ...
随机推荐
- 『集群』004 Slithice 集群分布式(多个客户端,基于中央服务器的集群服务)
Slithice 集群分布式(多个客户端,基于中央服务器的多个集群服务端) 案例Demo展示: 集群架构图 如下: 如上图,上图 展示了 这个集群 的 结构: >一个中央服务器(可以有多个),负 ...
- 接口自动化:HttpClient + TestNG + Java(一) - 接口测试概述+自动化环境搭建
1.1 接口测试简介 1.1.1 什么是接口测试 开始学习接口自动化测试之前,我们先要来了解什么是接口,以及什么是接口测试. 我们都知道,测试从级别上划分可以分为 组件测试 集成测试 系统测试 验收测 ...
- Scrum Mastery:有效利用组织的5个步骤
组织以什么样的方式能最大限度的发挥Scrum的优势?组织在哪些方面阻碍了个人的发展?Scrum是一种能使业务变得敏捷的框架.而组织恰恰需要变得敏捷.只是,组织本身有时候并没有足够的能力来帮助Scrum ...
- webpack初探 - (一)
什么webpack webpack是一个模块打包器.webpack把模块连同它的依赖一起打包生成包含这些模块的静态资源. 安装 在使用webpack时,建议不要把webpack安装到全局,如果多个项目 ...
- 【原创】Mindjet Manager思维导图软件云服务功能的使用方法
注:自己使用了mindjet manager来画思维导图已经有一段时间了,无疑mindjet manager的功能是很强大的,但是最近因为自己两台电脑都安装了mindjet manager,每 ...
- C#根据屏幕分辨率改变图片尺寸
最近工作中遇到一个问题,就是需要将程序文件夹中的图片根据此时电脑屏幕的分辨率来重新改变图片尺寸 以下为代码实现过程: 1.获取文件夹中的图片,此文件夹名为exe程序同目录下 //读取文件夹中文件 Di ...
- 成为一名Java架构师的必修课
一.热门框架源码学习 设计模式篇 Spring5源码解读篇 Mybatis篇 SpringBoot2篇 二. 微服务架构 架构设计篇 BAT互联网架构这些年的演进分析 国内外常见分布式系统架构状况介绍 ...
- 根据URL获取图片
背景:今天因为生产环境的系统界面图片无法显示被领导叼了一波,之前用Hutool工具类解析URL获取图片的,在生产环境上跑了一个多月都正常,嘣,今天突然发现周六下午后的图片统统显示异常,之后改为用jav ...
- C# 委托还能这样用
一直找不到一种能够让很多对象都能在几乎同时接收到通知的方法.介绍下目前在用的,希望能够抛砖引玉. 首先随便贴一下观察者模式的两接口,观察者模式自行搜索设计模式. public interface IS ...
- MySQL逻辑架构概述
1.MySQL逻辑架构 MySQL逻辑架构图 MySQL逻辑架构分四层 1.连接层:主要完成一些类似连接处理,授权认证及相关的安全方案. 2.服务层:在 MySQL据库系统处理底层数据之前的所有工作都 ...