假如说你想复制一个简单变量。很简单:

  1. int apples = 5;
  2. int pears = apples;

不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适用于该类情况。

但是如果你复制的是一个对象,情况就有些复杂了。

假设说我是一个beginner,我会这样写:

  1. class Student {
  2. private int number;
  3.  
  4. public int getNumber() {
  5. return number;
  6. }
  7.  
  8. public void setNumber(int number) {
  9. this.number = number;
  10. }
  11.  
  12. }
  13. public class Test {
  14.  
  15. public static void main(String args[]) {
  16. Student stu1 = new Student();
  17. stu1.setNumber(12345);
  18. Student stu2 = stu1;
  19.  
  20. System.out.println("学生1:" + stu1.getNumber());
  21. System.out.println("学生2:" + stu2.getNumber());
  22. }
  23. }

结果:

学生1:12345

学生2:12345

这里我们自定义了一个学生类,该类只有一个number字段。

我们新建了一个学生实例,然后将该值赋值给stu2实例。(Student stu2 = stu1;)

再看看打印结果,作为一个新手,拍了拍胸腹,对象复制不过如此,

难道真的是这样吗?

我们试着改变stu2实例的number字段,再打印结果看看:

  1. stu2.setNumber(54321);
  2.  
  3. System.out.println("学生1:" + stu1.getNumber());
  4. System.out.println("学生2:" + stu2.getNumber());

结果:

学生1:54321

学生2:54321

这就怪了,为什么改变学生2的学号,学生1的学号也发生了变化呢?

原因出在(stu2 = stu1) 这一句。该语句的作用是将stu1的引用赋值给stu2,

这样,stu1和stu2指向内存堆中同一个对象。如图:

那么,怎样才能达到复制一个对象呢?

是否记得万类之王Object。它有11个方法,有两个protected的方法,其中一个为clone方法。

在Java中所有的类都是缺省的继承自Java语言包中的Object类的,查看它的源码,你可以把你的JDK目录下的src.zip复制到其他地方然后解压,里面就是所有的源码。发现里面有一个访问限定符为protected的方法clone():

  1. /*
  2. Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.
  3. The general intent is that, for any object x, the expression:
  4. 1) x.clone() != x will be true
  5. 2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
  6. 3) x.clone().equals(x) will be true, this is not an absolute requirement.
  7. */
  8. protected native Object clone() throws CloneNotSupportedException;

仔细一看,它还是一个native方法,大家都知道native方法是非Java语言实现的代码,供Java程序调用的,因为Java程序是运行在JVM虚拟机上面的,要想访问到比较底层的与操作系统相关的就没办法了,只能由靠近操作系统的语言来实现。

  1. 第一次声明保证克隆对象将有单独的内存地址分配。
  2. 第二次声明表明,原始和克隆的对象应该具有相同的类类型,但它不是强制性的。
  3. 第三声明表明,原始和克隆的对象应该是平等的equals()方法使用,但它不是强制性的。

因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但是因为该方法是protected,所以都不能在类外进行访问。

要想对一个对象进行复制,就需要对clone方法覆盖。

什么要克隆?

  大家先思考一个问题,为什么需要克隆对象?直接new一个对象不行吗?

  答案是:克隆的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”就靠clone方法了。那么我把这个对象的临时属性一个一个的赋值给我新new的对象不也行嘛?可以是可以,但是一来麻烦不说,二来,大家通过上面的源码都发现了clone是一个native方法,就是快啊,在底层实现的。

  提个醒,我们常见的Object a=new Object();Object b;b=a;这种形式的代码复制的是引用,即对象在内存中的地址,a和b对象仍然指向了同一个对象。

  而通过clone方法赋值的对象跟原来的对象时同时独立存在的。

如何实现克隆

先介绍一下两种不同的克隆方法,浅克隆(ShallowClone)深克隆(DeepClone)

在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制,下面将对两者进行详细介绍。

一般步骤是(浅克隆):

1. 被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)

2. 覆盖clone()方法,访问修饰符设为public方法中调用super.clone()方法得到需要的复制对象。(native为本地方法)

下面对上面那个方法进行改造:

  1. class Student implements Cloneable{
  2. private int number;
  3.  
  4. public int getNumber() {
  5. return number;
  6. }
  7.  
  8. public void setNumber(int number) {
  9. this.number = number;
  10. }
  11.  
  12. @Override
  13. public Object clone() {
  14. Student stu = null;
  15. try{
  16. stu = (Student)super.clone();
  17. }catch(CloneNotSupportedException e) {
  18. e.printStackTrace();
  19. }
  20. return stu;
  21. }
  22. }
  23. public class Test {
  24. public static void main(String args[]) {
  25. Student stu1 = new Student();
  26. stu1.setNumber(12345);
  27. Student stu2 = (Student)stu1.clone();
  28.  
  29. System.out.println("学生1:" + stu1.getNumber());
  30. System.out.println("学生2:" + stu2.getNumber());
  31.  
  32. stu2.setNumber(54321);
  33.  
  34. System.out.println("学生1:" + stu1.getNumber());
  35. System.out.println("学生2:" + stu2.getNumber());
  36. }
  37. }

结果:

学生1:12345

学生2:12345

学生1:12345

学生2:54321

如果你还不相信这两个对象不是同一个对象,那么你可以看看这一句:

  1. System.out.println(stu1 == stu2); // false

上面的复制被称为浅克隆。

还有一种稍微复杂的深度复制:

我们在学生类里再加一个Address类。

  1. class Address {
  2. 2 private String add;
  3. 3
  4. 4 public String getAdd() {
  5. 5 return add;
  6. 6 }
  7. 7
  8. 8 public void setAdd(String add) {
  9. 9 this.add = add;
  10. 10 }
  11. 11
  12. 12 }
  13. 13
  14. 14 class Student implements Cloneable{
  15. 15 private int number;
  16. 16
  17. 17 private Address addr;
  18. 18
  19. 19 public Address getAddr() {
  20. 20 return addr;
  21. 21 }
  22. 22
  23. 23 public void setAddr(Address addr) {
  24. 24 this.addr = addr;
  25. 25 }
  26. 26
  27. 27 public int getNumber() {
  28. 28 return number;
  29. 29 }
  30. 30
  31. 31 public void setNumber(int number) {
  32. 32 this.number = number;
  33. 33 }
  34. 34
  35. 35 @Override
  36. 36 public Object clone() {
  37. 37 Student stu = null;
  38. 38 try{
  39. 39 stu = (Student)super.clone();
  40. 40 }catch(CloneNotSupportedException e) {
  41. 41 e.printStackTrace();
  42. 42 }
  43. 43 return stu;
  44. 44 }
  45. 45 }
  46. 46 public class Test {
  47. 47
  48. 48 public static void main(String args[]) {
  49. 49
  50. 50 Address addr = new Address();
  51. 51 addr.setAdd("杭州市");
  52. 52 Student stu1 = new Student();
  53. 53 stu1.setNumber(123);
  54. 54 stu1.setAddr(addr);
  55. 55
  56. 56 Student stu2 = (Student)stu1.clone();
  57. 57
  58. 58 System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
  59. 59 System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
  60. 60 }
  61. 61 }

结果:

学生1:123,地址:杭州市

学生2:123,地址:杭州市

乍一看没什么问题,真的是这样吗?

我们在main方法中试着改变addr实例的地址。

  1. addr.setAdd("西湖区");
  2.  
  3. System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
  4. System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());

结果:

  1. 学生1:123,地址:杭州市
  2. 学生2:123,地址:杭州市
  3. 学生1:123,地址:西湖区
  4. 学生2:123,地址:西湖区

这就奇怪了,怎么两个学生的地址都改变了?

原因是浅复制只是复制了addr变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象。

所以,为了达到真正的复制对象,而不是纯粹引用复制。我们需要将Address类可复制化,并且修改clone方法,完整代码如下:

  1. package abc;
  2. 2
  3. 3 class Address implements Cloneable {
  4. 4 private String add;
  5. 5
  6. 6 public String getAdd() {
  7. 7 return add;
  8. 8 }
  9. 9
  10. 10 public void setAdd(String add) {
  11. 11 this.add = add;
  12. 12 }
  13. 13
  14. 14 @Override
  15. 15 public Object clone() {
  16. 16 Address addr = null;
  17. 17 try{
  18. 18 addr = (Address)super.clone();
  19. 19 }catch(CloneNotSupportedException e) {
  20. 20 e.printStackTrace();
  21. 21 }
  22. 22 return addr;
  23. 23 }
  24. 24 }
  25. 25
  26. 26 class Student implements Cloneable{
  27. 27 private int number;
  28. 28
  29. 29 private Address addr;
  30. 30
  31. 31 public Address getAddr() {
  32. 32 return addr;
  33. 33 }
  34. 34
  35. 35 public void setAddr(Address addr) {
  36. 36 this.addr = addr;
  37. 37 }
  38. 38
  39. 39 public int getNumber() {
  40. 40 return number;
  41. 41 }
  42. 42
  43. 43 public void setNumber(int number) {
  44. 44 this.number = number;
  45. 45 }
  46. 46
  47. 47 @Override
  48. 48 public Object clone() {
  49. 49 Student stu = null;
  50. 50 try{
  51. 51 stu = (Student)super.clone(); //浅复制
  52. 52 }catch(CloneNotSupportedException e) {
  53. 53 e.printStackTrace();
  54. 54 }
  55. 55 stu.addr = (Address)addr.clone(); //深度复制
  56. 56 return stu;
  57. 57 }
  58. 58 }
  59. 59 public class Test {
  60. 60
  61. 61 public static void main(String args[]) {
  62. 62
  63. 63 Address addr = new Address();
  64. 64 addr.setAdd("杭州市");
  65. 65 Student stu1 = new Student();
  66. 66 stu1.setNumber(123);
  67. 67 stu1.setAddr(addr);
  68. 68
  69. 69 Student stu2 = (Student)stu1.clone();
  70. 70
  71. 71 System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
  72. 72 System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
  73. 73
  74. 74 addr.setAdd("西湖区");
  75. 75
  76. 76 System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
  77. 77 System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
  78. 78 }
  79. 79 }

结果:

  1. 学生1:123,地址:杭州市
  2. 学生2:123,地址:杭州市
  3. 学生1:123,地址:西湖区
  4. 学生2:123,地址:杭州市

这样结果就符合我们的想法了。

最后我们可以看看API里其中一个实现了clone方法的类:

java.util.Date:

  1. /**
  2. * Return a copy of this object.
  3. */
  4. public Object clone() {
  5. Date d = null;
  6. try {
  7. d = (Date)super.clone();
  8. if (cdate != null) {
  9. d.cdate = (BaseCalendar.Date) cdate.clone();
  10. }
  11. } catch (CloneNotSupportedException e) {} // Won't happen
  12. return d;
  13. }

该类其实也属于深度复制。

1、浅克隆

在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。

简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。

在Java语言中,通过覆盖Object类的clone()方法可以实现浅克隆

2、深克隆

在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。

简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。

在Java语言中,如果需要实现深克隆,可以通过覆盖Object类的clone()方法实现,也可以通过序列化(Serialization)等方式来实现。

如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。

序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。

扩展
Java语言提供的Cloneable接口和Serializable接口的代码非常简单,它们都是空接口,这种空接口也称为标识接口,标识接口中没有任何方法的定义,其作用是告诉JRE这些接口的实现类是否具有某个功能,如是否支持克隆、是否支持序列化等

解决多层克隆问题

如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。

如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。

  1. public class Outer implements Serializable{
  2. 2 private static final long serialVersionUID = 369285298572941L; //最好是显式声明ID
  3. 3 public Inner inner;
  4. 4  //Discription:[深度复制方法,需要对象及对象所有的对象属性都实现序列化] 
  5. 5 public Outer myclone() {
  6. 6 Outer outer = null;
  7. 7 try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
  8. 8 ByteArrayOutputStream baos = new ByteArrayOutputStream();
  9. 9 ObjectOutputStream oos = new ObjectOutputStream(baos);
  10. 10 oos.writeObject(this);
  11. 11       // 将流序列化成对象
  12. 12 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
  13. 13 ObjectInputStream ois = new ObjectInputStream(bais);
  14. 14 outer = (Outer) ois.readObject();
  15. 15 } catch (IOException e) {
  16. 16 e.printStackTrace();
  17. 17 } catch (ClassNotFoundException e) {
  18. 18 e.printStackTrace();
  19. 19 }
  20. 20 return outer;
  21. 21 }
  22. 22 }

Inner也必须实现Serializable,否则无法序列化:

  1. public class Inner implements Serializable{
  2. 2 private static final long serialVersionUID = 872390113109L; //最好是显式声明ID
  3. 3 public String name = "";
  4. 4
  5. 5 public Inner(String name) {
  6. 6 this.name = name;
  7. 7 }
  8. 8
  9. 9 @Override
  10. 10 public String toString() {
  11. 11 return "Inner的name值为:" + name;
  12. 12 }
  13. 13 }

这样也能使两个对象在内存空间内完全独立存在,互不影响对方的值。

总结

实现对象克隆有两种方式:

??1). 实现Cloneable接口并重写Object类中的clone()方法;

??2). 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。

注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是优于把问题留到运行时。

Java clone() 浅拷贝 深拷贝的更多相关文章

  1. Java clone() 方法克隆对象——深拷贝与浅拷贝

    基本数据类型引用数据类型特点 1.基本数据类型的特点:直接存储在栈(stack)中的数据 2.引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里 引用数据类型在栈中存储了指针,该指 ...

  2. 深入理解Java中的Clone与深拷贝和浅拷贝

    1.Java对象的创建 clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象. ...

  3. Java clone克隆方法 --深拷贝--浅拷贝 --原型模型

    什么是深拷贝? 什么是浅拷贝? 创建一个对象的方法有几种? 默认的Object方法中的clone是深拷贝还是浅拷贝? 为什么说很多深拷贝都是不彻底的深拷贝? 什么是原型模型,什么是原型模式? 原型模型 ...

  4. 浅谈Java中的深拷贝和浅拷贝(转载)

    浅谈Java中的深拷贝和浅拷贝(转载) 原文链接: http://blog.csdn.net/tounaobun/article/details/8491392 假如说你想复制一个简单变量.很简单: ...

  5. 浅谈Java中的深拷贝和浅拷贝

    转载: 浅谈Java中的深拷贝和浅拷贝 假如说你想复制一个简单变量.很简单: int apples = 5; int pears = apples; 不仅仅是int类型,其它七种原始数据类型(bool ...

  6. 内功心法 -- Java中的深拷贝和浅拷贝

    写在前面的话:读书破万卷,编码如有神--------------------------------------------------------------------这篇博客主要来谈谈" ...

  7. Java之浅拷贝与深拷贝

    ----?浅拷贝 --- 概念 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.简单说,浅拷贝就是只复制所考虑的对象,而不复制它所引用的对象 --- 实现方 ...

  8. Java基础(十三)--深拷贝和浅拷贝

    在上篇文章:Java基础(十二)--clone()方法,我们简单介绍了clone()的使用 clone()对于基本数据类型的拷贝是完全没问题的,但是如果是引用数据类型呢? @Data @NoArgsC ...

  9. java中的深拷贝与浅拷贝

    Java中对象的创建 clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象.那 ...

随机推荐

  1. Python学习:13.Python正则表达式

    一.正则表达式简介 正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配. Python 自1.5版本起增加了re 模块,它提供 Perl 风格的正则表达式模式. 就其本质 ...

  2. ios retainCount

    retainCount Important: Typically there should be no reason to explicitly ask an object what its reta ...

  3. 最近公共祖先 lca (施工ing)

    声明 咳咳,进入重难点的图论算法之一(敲黑板): 题目: 洛谷 P3379 先放标程,施工ing,以后补坑!!!(实在太难,一个模板这么长 [ 不过好像还是没有 AC自动机 长哎 ],注释都打半天,思 ...

  4. Springboot启动报Multiple Dockets with the same group name are not supported. The following duplicate groups were discovered.

    解决方法: 属于bean重复,根据错误提示剔除多于的Bean引用!

  5. Js中处理数据的常用内置方法

    以下方法均不会修改操作数本身,只是将操作数处理后的数据返回 1.丢弃小数部分,保留整数部分 parseInt(x);//丢弃x中的小数部分,保留x中的整数部分 2.Math对象常用的方法 ceil(x ...

  6. 20155226 实验一《Java开发环境的熟悉》实验报告

    20155226 实验一<Java开发环境的熟悉>实验报告 一. 实验内容及步骤 (一)使用JDK编译.运行简单的java程序 命令行下的程序开发 输入cd Code进入Code文件夹里 ...

  7. 【LG2183】[国家集训队]礼物

    [LG2183][国家集训队]礼物 题面 洛谷 题解 插曲:不知道为什么,一看到这个题目,我就想到了这个人... 如果不是有\(exLucas\),这题就是\(sb\)题... 首先,若\(\sum_ ...

  8. Zabbix学习之路(八)之自动化监控网络发现和主动注册

    1.网络发现  分两步走:创建发现规则(rule)和执行的动作(Action)(1)创建发现规则"Configuration"-->"Create discover ...

  9. MySQL入门篇(七)之Xtrabackup备份与恢复

    一.Xtrabackup介绍 MySQL冷备.mysqldump.MySQL热拷贝都无法实现对数据库进行增量备份.在实际生产环境中增量备份是非常实用的,如果数据大于50G或100G,存储空间足够的情况 ...

  10. MyBatis.Net 配置

    假设我们现在有这样的需求,要对学生信息进行管理 学生表有要以下要求 字段名称 数据类型 说明 stuNo 字符 学号,该列必填,为主键递增 stuName 字符 学生姓名,该列必填,要考虑姓氏可能是两 ...