https://www.cnblogs.com/shakinghead/p/7651502.html

https://blog.csdn.net/huwentao_totti/article/details/82755217

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

在程序中拷贝对象是很常见的,主要是为了在新的上下文环境中复用现有对象的部分或全部 数据。

Java中的对象拷贝主要分为:浅拷贝(Shallow Copy)、深拷贝(Deep Copy)。

先介绍一点铺垫知识:Java中的数据类型分为基本数据类型和引用数据类型。对于这两种数据类型,在进行赋值操作、用作方法参数或返回值时,会有值传递和引用(地址)传递的差别。

【关于 Map 的深浅拷贝 见 另一篇文章】

===============================

先给出结论:个人理解的深浅拷贝的区别

定义:      A:原对象 ;          B:A拷贝后的对象

(1)浅拷贝:

  • 基本数据类型(值传递):各自独立(各有一份),互不影响
  • 引用数据类型(引用传递)--除String:A变,B跟着变(两者指向同一个引用地址)

(2)深拷贝:A 和 B 的所有数据各自独立(各有一份),互不影响

关键区别在于:引用数据类型--除String   是同一份   还是不同的两份

===============================

目录

1、浅拷贝(Shallow Copy)

1.1、浅拷贝的实现方式(2种)

(1)、重写构造方法 ----一般的重写

(2)、重写clone()方法 ----最上层重写

2、深拷贝

2.1、深拷贝的实现方法(4种)

(1)、重写构造方法 ----引用数据类型开辟新空间

(2)、重写clone方法 ----每一层都重写

(3)、对象序列化 ----有工具类可用

(4)、json转换

(5)、原始手动赋值


1、浅拷贝(Shallow Copy)

①对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据。
②对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。

具体模型如图所示:可以看到基本数据类型的成员变量,对其值创建了新的拷贝。而引用数据类型的成员变量的实例仍然是只有一份,两个对象的该成员变量都指向同一个实例。

1.1、浅拷贝的实现方式(2种)

(1)、重写构造方法 ----一般的重写

拷贝构造方法指的是该类的构造方法参数为该类的对象。使用拷贝构造方法可以很好地完成浅拷贝,直接通过一个现有的对象创建出与该对象属性相同的新的对象。

代码参考如下:

/* 拷贝构造方法实现浅拷贝 */
public class CopyConstructor {public static void main(String[] args) {Age a=new Age(20);Person p1=new Person(a,"摇头耶稣");Person p2=new Person(p1);System.out.println("p1是"+p1);System.out.println("p2是"+p2);//修改p1的各属性值,观察p2的各属性值是否跟随变化p1.setName("小傻瓜");a.setAge(99);System.out.println("修改后的p1是"+p1);System.out.println("修改后的p2是"+p2);}
}class Person{//两个属性值:分别代表值传递和引用传递private Age age;private String name;public Person(Age age,String name) {this.age=age;this.name=name;}//拷贝构造方法public Person(Person p) {this.name=p.name;this.age=p.age;}public void setName(String name) {this.name=name;}public String toString() {return this.name+" "+this.age;}
}class Age{private int age;public Age(int age) {this.age=age;}public void setAge(int age) {this.age=age;}public int getAge() {return this.age;}public String toString() {return getAge()+"";}
}

运行结果为:

p1是摇头耶稣 20
p2是摇头耶稣 20
修改后的p1是小傻瓜 99
修改后的p2是摇头耶稣 99

结果分析:这里对Person类选择了两个具有代表性的属性值:一个是引用传递类型;另一个是字符串类型(属于常量)。

通过拷贝构造方法进行了浅拷贝,各属性值成功复制。其中,p1值传递部分的属性值发生变化时,p2不会随之改变;而引用传递部分属性值发生变化时,p2也随之改变。

要注意:如果在拷贝构造方法中,对引用数据类型变量逐一开辟新的内存空间,创建新的对象,也可以实现深拷贝。而对于一般的拷贝构造,则一定是浅拷贝。

(2)、重写clone()方法 ----最上层重写

Object类是类结构的根类,其中有一个方法为protected Object clone() throws CloneNotSupportedException,这个方法就是进行的浅拷贝。有了这个浅拷贝模板,我们可以通过调用clone()方法来实现对象的浅拷贝。但是需要注意:
1、Object类虽然有这个方法,但是这个方法是受保护的(被protected修饰),所以我们无法直接使用。
2、使用clone方法的类必须实现Cloneable接口,否则会抛出异常CloneNotSupportedException。
对于这两点,我们的解决方法是,在要使用clone方法的类中重写clone()方法,通过super.clone()调用Object类中的原clone方法。

参考代码如下:对Student类的对象进行拷贝,直接重写clone()方法,通过调用clone方法即可完成浅拷贝。

/* clone方法实现浅拷贝 */
public class ShallowCopy {public static void main(String[] args) {Age a=new Age(20);Student stu1=new Student("摇头耶稣",a,175);//通过调用重写后的clone方法进行浅拷贝Student stu2=(Student)stu1.clone();System.out.println(stu1.toString());System.out.println(stu2.toString());//尝试修改stu1中的各属性,观察stu2的属性有没有变化stu1.setName("大傻子");//改变age这个引用类型的成员变量的值a.setAge(99);//stu1.setaAge(new Age(99));    使用这种方式修改age属性值的话,stu2是不会跟着改变的。因为创建了一个新的Age类对象而不是改变原对象的实例值stu1.setLength(216);System.out.println(stu1.toString());System.out.println(stu2.toString());}
}/** 创建年龄类*/
class Age{//年龄类的成员变量(属性)private int age;//构造方法public Age(int age) {this.age=age;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String toString() {return this.age+"";}
}
/** 创建学生类*/
class Student implements Cloneable{//学生类的成员变量(属性),其中一个属性为类的对象private String name;private Age aage;private int length;//构造方法,其中一个参数为另一个类的对象public Student(String name,Age a,int length) {this.name=name;this.aage=a;this.length=length;}//eclipe中alt+shift+s自动添加所有的set和get方法public String getName() {return name;}public void setName(String name) {this.name = name;}public Age getaAge() {return this.aage;}public void setaAge(Age age) {this.aage=age;}public int getLength() {return this.length;}public void setLength(int length) {this.length=length;}//设置输出的字符串形式public String toString() {return "姓名是: "+this.getName()+", 年龄为: "+this.getaAge().toString()+", 长度是: "+this.getLength();}//重写Object类的clone方法public Object clone() {Object obj=null;//调用Object类的clone方法,返回一个Object实例try {obj= super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return obj;}
}

运行结果如下:

姓名是: 摇头耶稣, 年龄为: 20, 长度是: 175
姓名是: 摇头耶稣, 年龄为: 20, 长度是: 175
姓名是: 大傻子, 年龄为: 99, 长度是: 216
姓名是: 摇头耶稣, 年龄为: 99, 长度是: 175

其中:Student类的成员变量我有代表性地设置了三种:
基本数据类型的成员变量length,引用数据类型的成员变量age 和 字符串String类型的name.

分析结果可以验证:

基本数据类型是值传递,所以修改值后不会影响另一个对象的该属性值;

引用数据类型是地址传递(引用传递),所以修改值后另一个对象的该属性值会同步被修改。

String类型非常特殊,所以我额外设置了一个字符串类型的成员变量来进行说明。
首先,String类型属于引用数据类型,不属于基本数据类型,但是String类型的数据是存放在常量池中的,也就是无法修改的!也就是说,当我将name属性从“摇头耶稣”改为“大傻子"后,并不是修改了这个数据的值,而是把这个数据的引用从指向”摇头耶稣“这个常量改为了指向”大傻子“这个常量。在这种情况下,另一个对象的name属性值仍然指向”摇头耶稣“不会受到影响。

2、深拷贝(Deep Copy)

首先介绍对象图的概念。设想一下,一个类有一个对象,其成员变量中又有一个对象,该对象指向另一个对象,另一个对象又指向另一个对象,直到一个确定的实例。这就形成了对象图。那么,对于深拷贝来说,不仅要复制对象的所有基本数据类型的成员变量值,还要为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象图进行拷贝!

简单地说,深拷贝对引用数据类型的成员变量的对象图中所有的对象都开辟了内存空间;而浅拷贝只是传递地址指向,新的对象并没有对引用数据类型创建内存空间。

深拷贝模型如图所示:可以看到所有的成员变量都进行了复制。

因为创建内存空间和拷贝整个对象图,所以深拷贝相比于浅拷贝速度较慢并且花销较大。

2.1、深拷贝的实现方法(4种)

(1)、重写构造方法 ----引用数据类型开辟新空间

如果在拷贝构造方法中,对引用数据类型变量逐一开辟新的内存空间,创建新的对象,也可以实现深拷贝。

(2)、重写clone方法 ----每一层都重写

与通过重写clone方法实现浅拷贝的基本思路一样,只需要为对象图的每一层的每一个对象都实现Cloneable接口并重写clone方法,最后在最顶层的类的重写的clone方法中调用所有的clone方法即可实现深拷贝。
简单的说就是:每一层的每个对象都进行浅拷贝=深拷贝

参考代码如下:

package linearList;
/* 层次调用clone方法实现深拷贝 */
public class DeepCopy {public static void main(String[] args) {Age a=new Age(20);Student stu1=new Student("摇头耶稣",a,175);//通过调用重写后的clone方法进行浅拷贝Student stu2=(Student)stu1.clone();System.out.println(stu1.toString());System.out.println(stu2.toString());System.out.println();//尝试修改stu1中的各属性,观察stu2的属性有没有变化stu1.setName("大傻子");//改变age这个引用类型的成员变量的值a.setAge(99);//stu1.setaAge(new Age(99));    使用这种方式修改age属性值的话,stu2是不会跟着改变的。因为创建了一个新的Age类对象而不是改变原对象的实例值stu1.setLength(216);System.out.println(stu1.toString());System.out.println(stu2.toString());}
}/** 创建年龄类*/
class Age implements Cloneable{//年龄类的成员变量(属性)private int age;//构造方法public Age(int age) {this.age=age;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String toString() {return this.age+"";}//重写Object的clone方法public Object clone() {Object obj=null;try {obj=super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return obj;}
}
/** 创建学生类*/
class Student implements Cloneable{//学生类的成员变量(属性),其中一个属性为类的对象private String name;private Age aage;private int length;//构造方法,其中一个参数为另一个类的对象public Student(String name,Age a,int length) {this.name=name;this.aage=a;this.length=length;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Age getaAge() {return this.aage;}public void setaAge(Age age) {this.aage=age;}public int getLength() {return this.length;}public void setLength(int length) {this.length=length;}public String toString() {return "姓名是: "+this.getName()+", 年龄为: "+this.getaAge().toString()+", 长度是: "+this.getLength();}//重写Object类的clone方法public Object clone() {Object obj=null;//调用Object类的clone方法——浅拷贝try {obj= super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}//调用Age类的clone方法进行深拷贝//先将obj转化为学生类实例Student stu=(Student)obj;//学生类实例的Age对象属性,调用其clone方法进行拷贝stu.aage=(Age)stu.getaAge().clone();return obj;}
}

运行结果如下:

姓名是: 摇头耶稣, 年龄为: 20, 长度是: 175
姓名是: 摇头耶稣, 年龄为: 20, 长度是: 175
姓名是: 大傻子, 年龄为: 99, 长度是: 216
姓名是: 摇头耶稣, 年龄为: 20, 长度是: 175

分析结果可以验证:进行了深拷贝之后,无论是什么类型的属性值的修改,都不会影响另一个对象的属性值。

(3)、对象序列化 ----有工具类可用

也可以使用 SerializationUtils 的 clone(Object obj) 方法

要求拷贝的对象实现了Serializable,Map不行,使用HashMap即可

虽然层次调用clone方法可以实现深拷贝,但是显然代码量实在太大。特别对于属性数量比较多、层次比较深的类而言,每个类都要重写clone方法太过繁琐。

将对象序列化为字节序列后,默认会将该对象的整个对象图进行序列化,再通过反序列即可完美地实现深拷贝。

参考代码如下:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;/* 通过序列化实现深拷贝 */
public class DeepCopyBySerialization {public static void main(String[] args) throws IOException, ClassNotFoundException  {Age a=new Age(20);Student stu1=new Student("摇头耶稣",a,175);//通过序列化方法实现深拷贝ByteArrayOutputStream bos=new ByteArrayOutputStream();ObjectOutputStream oos=new ObjectOutputStream(bos);oos.writeObject(stu1);oos.flush();ObjectInputStream ois=new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));Student stu2=(Student)ois.readObject();System.out.println(stu1.toString());System.out.println(stu2.toString());System.out.println();//尝试修改stu1中的各属性,观察stu2的属性有没有变化stu1.setName("大傻子");//改变age这个引用类型的成员变量的值a.setAge(99);stu1.setLength(216);System.out.println(stu1.toString());System.out.println(stu2.toString());}
}/** 创建年龄类*/
class Age implements Serializable{//年龄类的成员变量(属性)private int age;//构造方法public Age(int age) {this.age=age;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String toString() {return this.age+"";}
}
/** 创建学生类*/
class Student implements Serializable{//学生类的成员变量(属性),其中一个属性为类的对象private String name;private Age aage;private int length;//构造方法,其中一个参数为另一个类的对象public Student(String name,Age a,int length) {this.name=name;this.aage=a;this.length=length;}//eclipe中alt+shift+s自动添加所有的set和get方法public String getName() {return name;}public void setName(String name) {this.name = name;}public Age getaAge() {return this.aage;}public void setaAge(Age age) {this.aage=age;}public int getLength() {return this.length;}public void setLength(int length) {this.length=length;}//设置输出的字符串形式public String toString() {return "姓名是: "+this.getName()+", 年龄为: "+this.getaAge().toString()+", 长度是: "+this.getLength();}
}

运行结果为:

姓名是: 摇头耶稣, 年龄为: 20, 长度是: 175
姓名是: 摇头耶稣, 年龄为: 20, 长度是: 175
姓名是: 大傻子, 年龄为: 99, 长度是: 216
姓名是: 摇头耶稣, 年龄为: 20, 长度是: 175

可以通过很简洁的代码即可完美实现深拷贝。不过要注意的是,如果某个属性被transient修饰,那么该属性就无法被拷贝了。

(4)、json转换

用fastjson从Object转成json,然后转回object。本质上是反射

 private Object deepCopyByJson(Object obj) {String json = JSON.toJSONString(obj);return JSON.parseObject(json, Object.class);}

(5)、原始手动赋值

代码啰嗦,基本不用

Java 浅拷贝和深拷贝的理解和实现方式相关推荐

  1. java什么地方要用深拷贝,关于 Java 浅拷贝、深拷贝,你真的知道了吗?

    这是今天我们在技术群里面讨论的一个知识点,讨论的相当激烈,由于对这一块使用的比较少,所以对这一块多少有些盲区.这篇文章总结了所讨论的内容,希望这篇文章对你有所帮助. 在 Java 开发中,对象拷贝或者 ...

  2. Java 浅拷贝、深拷贝,你知多少?

    在 Java 开发中,对象拷贝或者说对象克隆是常有的事,对象克隆最终都离不开直接赋值.浅拷贝.深拷贝 这三种方式,其中直接赋值应该是我们最常用的一种方式吧,对于浅拷贝和深拷贝可能用的少,所以或多或少存 ...

  3. javascript中浅拷贝和深拷贝的理解

    javascript中浅拷贝和深拷贝的理解 什么是拷贝? 简单地说就是复制,对数据的复制 浅拷贝:改变拷贝者的值,被拷贝者的值也会变化 深拷贝:改变拷贝者的值,被拷贝者的值不会变化 由于基本数据类型是 ...

  4. Python中浅拷贝和深拷贝的理解与研究

    Python中浅拷贝和深拷贝的理解与研究 单层浅拷贝 import copy a = 1 # 不可变数据类型 copy_a = copy.copy(a) print(id(a),id(copy_a)) ...

  5. Python中浅拷贝和深拷贝的理解与研究 1

    Python中浅拷贝和深拷贝的理解与研究 单层浅拷贝 import copy a = 1 # 不可变数据类型 copy_a = copy.copy(a) print(id(a),id(copy_a)) ...

  6. Java浅拷贝和深拷贝(一文足矣),及String类型的坑。

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/q258523454/article/d ...

  7. Java浅拷贝和深拷贝的方式

    文章目录 1. 前言 2. 概念介绍 2.1 拷贝 / 克隆的概念 2.2 为什么需要拷贝方法? 2.3 什么是浅拷贝?浅拷贝和深拷贝的区别是什么? 3. 深拷贝的实现方式 3.1 手动深拷贝 3.2 ...

  8. Java提高篇 —— Java浅拷贝和深拷贝

    一.前言 我们知道在Java中存在这个接口Cloneable,实现该接口的类都会具备被拷贝的能力,同时拷贝是在内存中进行,在性能方面比我们直接通过new生成对象来的快,特别是在大对象的生成上,使得性能 ...

  9. 拷贝构造函数的调用以及浅拷贝与深拷贝的理解

    今天一直在研究拷贝构造函数相关的东西,我这个大四老狗感觉又回到了大一学C++的时候.瞎捣鼓了一天,略微还是有些收获的,趁着脑子中的概念正热,把自己的心得赶紧整理出来分享给大家. ​       首先简 ...

最新文章

  1. 计算机基本知识培训稿,计算机基础知识培训稿.doc
  2. 调用max函数求两个数中较大值
  3. iis7.5 php虚拟站点目录设置,windows2008中IIS7.5环境下 Fastcgi模式PHP配置教程
  4. python 输入列表 返回每个元素出现的次数
  5. Selenium的一些技巧与错误处理
  6. 终端IO--unix环境高级编程读书笔记
  7. php 事务回滚,php实现事务回滚的方法
  8. Mac系统如何删除.DS_Store文件且不再生
  9. wordpresd免登录发布接口php_实现wordpress的ajax接口请求学会admin-ajax.php的利用
  10. 华为vrrp默认优先级_华为vrrp配置实例
  11. 电芯容量在前期循环中容量增加_锂电池随着使用次数增加而最大容量下降,为什么...
  12. xay loves or
  13. 生成排列(全排列)的两种写法
  14. CentOS7.6搭建开源WCP知识管理系统
  15. 京东商城源码_选择源码的经验方法,你get了吗!
  16. GBase 8a事务控制
  17. 陌生人社交网络大起底:谁是下一个陌陌?
  18. 经典动态规划OJ题目:接雨水or接青豆(多种方法,附详详细思维过程、解析及源码)
  19. 20P79 pr预设模板1260个无缝转场 Seamless Pan Transitions
  20. c语言16进制直接文本输出,c语言问题求助:将16进制文本转10进制并输出文本

热门文章

  1. Scala进阶_函数式编程(过滤丶排序丶分组丶聚合)
  2. Facebook联手纽约大学,要把核磁共振成像时间缩短10倍
  3. Python使用tkinter库制作带有Laber标签、Entry文本框、Progressbar进度条、text日志框等元素的GUI操作界面
  4. 不会做动画的程序猿不是好的动画师(如何用css3动画做动画)
  5. 王道程序员求职宝典 pdf
  6. 【深度学习】图像分割的难点
  7. 油管视频目录正则整理
  8. charles安装证书流程
  9. NetApp ADP (Advanced drive partitioning) 介绍
  10. “去中心化”是区块链的目的吗