一看就懂的,java深拷贝浅拷贝

将一个对象的引用复制给另外一个对象,一共有三种方式。第一种方式是直接赋值,第二种方式是浅拷贝,第三种是深拷贝。所以大家知道了哈,这三种概念实际上都是为了拷贝对象啊。
 

1、直接赋值

好,下面我们先看第一种方式,直接赋值。在Java中,A a1 = a2,我们需要理解的是这实际上复制的是引用,也就是说a1和a2指向的是同一个对象。因此,当a1变化的时候,a2里面的成员变量也会跟着变化。各位,请看下面的代码吧!
 
  1. /* 建立类 */
  2. class Resume {
  3. private String name; //姓名
  4. private String sex; //性别
  5. private int age; //年龄
  6. private String experience; //工作经历
  7.  
  8. public Resume(String name, String sex, int age) {
  9. this.name = name;
  10. this.sex = sex;
  11. this.age = age;
  12. }
  13.  
  14. public void setAge(int age) {
  15. this.age = age;
  16. }
  17. public int getAge() {
  18. return age;
  19. }
  20.  
  21. public void setExperience(String experience) {
  22. this.experience = experience;
  23. }
  24. public String getExperience() {
  25. return experience;
  26. }
  27.  
  28. public void displayResume() {
  29. System.out.println("姓名:"+name+" 性别:"+sex+" 年龄:"+age);
  30. System.out.println("工作经历:"+experience);
  31. }
  32. }
  33.  
  34. public class MainClass {
  35. public static void main(String[] args) {
  36. Resume zhangsan = new Resume("zhangsan","男",24);
  37. zhangsan.setExperience("2009-2013就读于家里蹲大学,精通JAVA,C,C++,C#等代码复制");
  38. zhangsan.displayResume();
  39. Resume zhangsan1 = zhangsan;
  40. zhangsan1.setExperience("2009-2013就读于家里蹲大学,精通JAVA,C,C++,C#等");
  41. zhangsan.displayResume();
  42. zhangsan1.displayResume();
  43. }
  44. }

程序运行结果

  1. 姓名:zhangsan 性别:男 年龄:24
  2. 工作经历:2009-2013就读于家里蹲大学,精通JAVA,C,C++,C#等代码复制
  3. 姓名:zhangsan 性别:男 年龄:24
  4. 工作经历:2009-2013就读于家里蹲大学,精通JAVA,C,C++,C#等
  5. 姓名:zhangsan 性别:男 年龄:24
  6. 工作经历:2009-2013就读于家里蹲大学,精通JAVA,C,C++,C#等

在本程序中,生成了一份zhangsan的简历。之后又复制了一份简历zhangsan1,可见zhangsan1中工作经历发生变化时,zhangsan的工作经历也发生了变化。

2、浅拷贝

上面直接赋值的结果,有时候可能并不是我们所想要的。就像我们投简历的时候,可能会根据应聘公司的类型做出相应的调整,如果是投技术类的工作可能会偏技术一点;如果是投国企啊什么之类的,社会经历学生工作什么的可能也是很重要的一部分。所以我们不需要当我们修改一份简历的时候,所有的简历都变调。不然到时候投技术类的公司又得改回来。说了这么多,我们也就是希望,把a1赋值给a2之后,a1和a2能保持独立,不要互相影响。
 
实现上面想法之一的方法就是Object的Clone()函数了。在这里,我们需要了解clone()主要做了些什么,创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。
好,我们先看这一段话的前一部分,如果字段是值类型,则直接复制。如下面程序所示
 
  1. /* 建立类,实现Clone方法 */
  2. class Resume implements Cloneable{
  3. private String name; //姓名
  4. private String sex; //性别
  5. private int age; //年龄
  6. private String experience; //工作经历
  7.  
  8. public Resume(String name, String sex, int age) {
  9. this.name = name;
  10. this.sex = sex;
  11. this.age = age;
  12. }
  13.  
  14. public void setAge(int age) {
  15. this.age = age;
  16. }
  17. public int getAge() {
  18. return age;
  19. }
  20.  
  21. public void setExperience(String experience) {
  22. this.experience = experience;
  23. }
  24. public String getExperience() {
  25. return experience;
  26. }
  27.  
  28. public void displayResume() {
  29. System.out.println("姓名:"+name+" 性别:"+sex+" 年龄:"+age);
  30. System.out.println("工作经历:"+experience);
  31. }
  32.  
  33. public Object clone() {
  34. try {
  35. return (Resume)super.clone();
  36. } catch (Exception e) {
  37. e.printStackTrace();
  38. return null;
  39. }
  40. }
  41. }
  42.  
  43. public class MainClass {
  44. public static void main(String[] args) {
  45. Resume zhangsan = new Resume("zhangsan","男",24);
  46. zhangsan.setExperience("2009-2013就读于家里蹲大学,精通JAVA,C,C++,C#等代码拷贝和粘贴");
  47. zhangsan.displayResume();
  48. Resume zhangsan1 = (Resume)zhangsan.clone();
  49. zhangsan1.setAge(23);
  50. zhangsan1.displayResume();
  51. Resume zhangsan2 = (Resume)zhangsan.clone();
  52. zhangsan2.setExperience("2009-2013就读于家里蹲大学,精通JAVA,C,C++,C#等代码");
  53. zhangsan2.displayResume();
  54. zhangsan.displayResume();
  55. }
  56. }

程序运行结果

  1. 姓名:zhangsan 性别:男 年龄:24
  2. 工作经历:2009-2013就读于家里蹲大学,精通JAVA,C,C++,C#等代码拷贝和粘贴
  3. 姓名:zhangsan 性别:男 年龄:23
  4. 工作经历:2009-2013就读于家里蹲大学,精通JAVA,C,C++,C#等代码拷贝和粘贴
  5. 姓名:zhangsan 性别:男 年龄:24
  6. 工作经历:2009-2013就读于家里蹲大学,精通JAVA,C,C++,C#等代码
  7. 姓名:zhangsan 性别:男 年龄:24
  8. 工作经历:2009-2013就读于家里蹲大学,精通JAVA,C,C++,C#等代码拷贝和粘贴
由程序的运行结果可以看出,我们实现了a1和a2引用的独立。
 
但是什么叫“如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。”,到底什么意思?不用着急,我们接下来看下面一段程序:
  1. class Experience {
  2.  
  3. private String educationBackground;
  4. private String skills;
  5.  
  6. public void setExperience(String educationBackground, String skills) {
  7. // TODO Auto-generated constructor stub
  8. this.educationBackground = educationBackground;
  9. this.skills = skills;
  10. }
  11. public String toString() {
  12. return educationBackground + skills;
  13. }
  14. }
  15.  
  16. /* 建立类,实现Clone方法 */
  17. class Resume implements Cloneable{
  18. private String name; //姓名
  19. private String sex; //性别
  20. private int age; //年龄
  21. private Experience experience; //工作经历
  22.  
  23. public Resume(String name, String sex, int age) {
  24. this.name = name;
  25. this.sex = sex;
  26. this.age = age;
  27. this.experience = new Experience();
  28. }
  29.  
  30. public void setAge(int age) {
  31. this.age = age;
  32. }
  33. public int getAge() {
  34. return age;
  35. }
  36.  
  37. public Experience getExperience() {
  38. return experience;
  39. }
  40.  
  41. public void setExperience(String educationBackground, String skills) {
  42. experience.setExperience(educationBackground, skills);
  43. }
  44.  
  45. public void displayResume() {
  46. System.out.println("姓名:"+name+" 性别:"+sex+" 年龄:"+age);
  47. System.out.println("工作经历:"+experience.toString());
  48. }
  49.  
  50. public Object clone() {
  51. try {
  52. return (Resume)super.clone();
  53. } catch (Exception e) {
  54. e.printStackTrace();
  55. return null;
  56. }
  57. }
  58. }
  59.  
  60. public class MainClass {
  61. public static void main(String[] args) {
  62. Resume zhangsan = new Resume("zhangsan","男",24);
  63. zhangsan.setExperience("2009-2013就读于家里蹲大学","精通JAVA,C,C++,C#等代码拷贝和粘贴");
  64. zhangsan.displayResume();
  65.  
  66. Resume zhangsan2 = (Resume)zhangsan.clone();
  67. zhangsan2.setExperience("2009-2013就读于家里蹲大学","精通JAVA,C,C++,C#等");
  68. zhangsan2.displayResume();
  69. zhangsan.displayResume();
  70. zhangsan2.displayResume();
  71. }
  72. }

程序运行结果:

  1. 姓名:zhangsan 性别:男 年龄:24
  2. 工作经历:2009-2013就读于家里蹲大学精通JAVA,C,C++,C#等代码拷贝和粘贴
  3. 姓名:zhangsan 性别:男 年龄:24
  4. 工作经历:2009-2013就读于家里蹲大学精通JAVA,C,C++,C#等
  5. 姓名:zhangsan 性别:男 年龄:24
  6. 工作经历:2009-2013就读于家里蹲大学精通JAVA,C,C++,C#等
  7. 姓名:zhangsan 性别:男 年龄:24
  8. 工作经历:2009-2013就读于家里蹲大学精通JAVA,C,C++,C#等

我们看一下上面两段程序差异在哪儿,第一段程序的工作经历是作为Resume类的一个普通的成员变量,也就是值属性。而后面一段程序中,工作经历Experience是一个类。结合上面程序的运行结果,我们再来理解“如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。”其实也就是说,zhangsan和zhangsan2里面的Experience类指向的是同一个对象嘛!那不管是zhangsan里面的Experience变化,还是zhangsan2里面的Experience变化都会影响另外一个啊。

3、深拷贝

其实出现问题的关键就在于clone()方法上,我们知道该clone()方法是使用Object类的clone()方法,但是该方法存在一个缺陷,它并不会将对象的所有属性全部拷贝过来,而是有选择性的拷贝,基本规则如下:

1、 基本类型

如果变量是基本很类型,则拷贝其值,比如int、float等。

2、 对象

如果变量是一个实例对象,则拷贝其地址引用,也就是说此时新对象与原来对象是公用该实例变量。

3、 String字符串

若变量为String字符串,则拷贝其地址引用。但是在修改时,它会从字符串池中重新生成一个新的字符串,原有紫都城对象保持不变。

基于上面上面的规则,我们很容易发现问题的所在,他们三者公用一个对象,张三修改了该邮件内容,则李四和王五也会修改,所以才会出现上面的情况。对于这种情况我们还是可以解决的,只需要在clone()方法里面新建一个对象,然后张三引用该对象即可:

  1. rotected Person clone() {
  2. Person person = null;
  3. try {
  4. person = (Person) super.clone();
  5. person.setEmail(new Email(person.getEmail().getObject(),person.getEmail().getContent()));
  6. } catch (CloneNotSupportedException e) {
  7. e.printStackTrace();
  8. }
  9.  
  10. return person;
  11. }

所以:浅拷贝只是Java提供的一种简单的拷贝机制,不便于直接使用。

对于上面的解决方案还是存在一个问题,若我们系统中存在大量的对象是通过拷贝生成的,如果我们每一个类都写一个clone()方法,并将还需要进行深拷贝,新建大量的对象,这个工程是非常大的,这里我们可以利用序列化来实现对象的拷贝。

如何利用序列化来完成对象的拷贝呢?在内存中通过字节流的拷贝是比较容易实现的。把母对象写入到一个字节流中,再从字节流中将其读出来,这样就可以创建一个新的对象了,并且该新对象与母对象之间并不存在引用共享的问题,真正实现对象的深拷贝。

  1. public class CloneUtils {
  2. @SuppressWarnings("unchecked")
  3. public static <T extends Serializable> T clone(T obj){
  4. T cloneObj = null;
  5. try {
  6. //写入字节流
  7. ByteArrayOutputStream out = new ByteArrayOutputStream();
  8. ObjectOutputStream obs = new ObjectOutputStream(out);
  9. obs.writeObject(obj);
  10. obs.close();
  11.  
  12. //分配内存,写入原始对象,生成新对象
  13. ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
  14. ObjectInputStream ois = new ObjectInputStream(ios);
  15. //返回生成的新对象
  16. cloneObj = (T) ois.readObject();
  17. ois.close();
  18. } catch (Exception e) {
  19. e.printStackTrace();
  20. }
  21. return cloneObj;
  22. }
  23. }

使用该工具类的对象必须要实现Serializable接口,否则是没有办法实现克隆的。

  1. public class Person implements Serializable{
  2. private static final long serialVersionUID = 2631590509760908280L;
  3.  
  4. ..................
  5. //去除clone()方法
  6.  
  7. }
  8.  
  9. public class Email implements Serializable{
  10. private static final long serialVersionUID = 1267293988171991494L;
  11.  
  12. ....................
  13. }

所以使用该工具类的对象只要实现Serializable接口就可实现对象的克隆,无须继承Cloneable接口实现clone()方法。

  1. public class Client {
  2. public static void main(String[] args) {
  3. //写封邮件
  4. Email email = new Email("请参加会议","请与今天12:30到二会议室参加会议...");
  5.  
  6. Person person1 = new Person("张三",email);
  7.  
  8. Person person2 = CloneUtils.clone(person1);
  9. person2.setName("李四");
  10. Person person3 = CloneUtils.clone(person1);
  11. person3.setName("王五");
  12. person1.getEmail().setContent("请与今天12:00到二会议室参加会议...");
  13.  
  14. System.out.println(person1.getName() + "的邮件内容是:" + person1.getEmail().getContent());
  15. System.out.println(person2.getName() + "的邮件内容是:" + person2.getEmail().getContent());
  16. System.out.println(person3.getName() + "的邮件内容是:" + person3.getEmail().getContent());
  17. }
  18. }
  19. -------------------
  20. Output:
  21. 张三的邮件内容是:请与今天12:00到二会议室参加会议...
  22. 李四的邮件内容是:请与今天12:30到二会议室参加会议...
  23. 王五的邮件内容是:请与今天12:30到二会议室参加会议...
 

Java 浅拷贝和深拷贝的更多相关文章

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

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

  2. Java 浅拷贝和深拷贝的理解和实现方式

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

  3. java 浅拷贝和深拷贝 对象克隆clone

    分一下几点讨论: 为什么要克隆? 如何实现克隆 浅克隆和深克隆 解决多层克隆问题 总结 一:为什么要克隆? 大家先思考一个问题,为什么需要克隆对象?直接new一个对象不行吗? 答案是:克隆的对象可能包 ...

  4. 浅谈java浅拷贝和深拷贝

    前言:深拷贝和浅拷贝的区别是什么? 浅拷贝:被复制的对象的所有变量都含有原来对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之, 浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象.深拷 ...

  5. 【转】Java 浅拷贝和深拷贝的理解和实现方式

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

  6. Java 浅拷贝,深拷贝

         从Java 强引用.软引用,弱引用http://blog.csdn.net/jltxgcy/article/details/35558465一文中,我们看到把一个对象赋值给另一个对象,本质上 ...

  7. java浅拷贝和深拷贝(基础也是很重要的)

    对象的copy你兴许只是懵懂,或者是并没在意,来了解下吧. 对于的github基础代码https://github.com/chywx/JavaSE 最近学习c++,跟java很是相像,在慕课网学习c ...

  8. Java浅拷贝与深拷贝(思维导图)

    图1 拷贝思维导图(点击查看图片) 1,拷贝 有两个相同属性的对象A和B,A拥有初始化值,将其值拷贝到B中,使得B拥有与A“相同”数据的属性!注意这里的相同我有加双引号! 相同可能表示这么几个意思:① ...

  9. java浅拷贝和深拷贝

    转:http://blog.csdn.net/u014727260/article/details/55003402 实现clone的2点: 1,clone方法是Object类的一个方法,所以任何一个 ...

随机推荐

  1. scala 2.11.x/spec/03-types.md

    scala/spec/03-types.md title: Types layout: default chapter: 3 --- Types Type ::= FunctionArgTypes ' ...

  2. Github远程推送一直Everything up-to-date

    问题描述: Github远程推送一直Everything up-to-date,但其实并没有推送成功,远程库中没有更新文件 可能原因分析及解决方法: "git push with no ad ...

  3. Java作业五(2017-10-15)

    /*3-6.程序员;龚猛*/ 1 package zhenshu; import java.util.Scanner; public class text { public static void m ...

  4. 深入理解Spring Redis的使用 (四)、RedisTemplate执行Redis脚本

    对于Redis脚本使用过的同学都知道,这个主要是为了防止竞态条件而用的.因为脚本是顺序执行的.(不用担心效率问题)比如我在工作用,用来设置考试最高分. 如果还没有用过的话,先去看Redis脚本的介绍, ...

  5. [Swift]LeetCode447. 回旋镖的数量 | Number of Boomerangs

    Given n points in the plane that are all pairwise distinct, a "boomerang" is a tuple of po ...

  6. GraphQL-前端开发的利剑与桥梁

    GraphQL-前端开发的利剑与桥梁 基本概念 GraphQL GraphQL 是一种用于 API 的查询语言,由Facebook开发和开源,是使用基于类型系统来执行查询的服务端运行时(类型系统由你的 ...

  7. 如何看待Google欲回归中国事件

    最近一条新闻刷爆了朋友圈: 8 月 6 日,<人民日报>在它位于 Facebook.Twitter 社交媒体平台的官方账号上发布了一篇标题为<Stability prerequisi ...

  8. 我要曝光!CDN 省钱大法!

    七夕节刚过去,小明却特别郁闷,因为七夕当天,他错过了和远在北京的女神表白的机会.事情的经过是怎样的呢?为了在七夕当天送给自己女神一件礼物,小明在某购物网站上花重金购买了特别的礼物,礼物是从广东发送,结 ...

  9. 学习-xlsxwriter模块

    Xlsx是python用来构造xlsx文件的模块,可以向excel2007+中写text,numbers,formulas 公式以及hyperlinks超链接. 可以完成xlsx文件的自动化构造,包括 ...

  10. linux安装字体方法

    1.查看系统中文字体 #fc-list :lang=zh 2.如果提示commont not fount 说明为安装fontconfig 3.安装fontconfig #yum -y install ...