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

int apples = 5;
int pears = apples;  

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

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

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

class Student {  private int number;  public int getNumber() {  return number;  }  public void setNumber(int number) {  this.number = number;  }  }
public class Test {  public static void main(String args[]) {  Student stu1 = new Student();  stu1.setNumber(12345);  Student stu2 = stu1;  System.out.println("学生1:" + stu1.getNumber());  System.out.println("学生2:" + stu2.getNumber());  }
}  

结果:

学生1:12345

学生2:12345

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

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

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

难道真的是这样吗?

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

stu2.setNumber(54321);  System.out.println("学生1:" + stu1.getNumber());
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():

/*
Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.
The general intent is that, for any object x, the expression:
1) x.clone() != x will be true
2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
3) x.clone().equals(x) will be true, this is not an absolute requirement.
*/
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为本地方法)

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

class Student implements Cloneable{  private int number;  public int getNumber() {  return number;  }  public void setNumber(int number) {  this.number = number;  }  @Override  public Object clone() {  Student stu = null;  try{  stu = (Student)super.clone();  }catch(CloneNotSupportedException e) {  e.printStackTrace();  }  return stu;  }
}
public class Test {  public static void main(String args[]) {  Student stu1 = new Student();  stu1.setNumber(12345);  Student stu2 = (Student)stu1.clone();  System.out.println("学生1:" + stu1.getNumber());  System.out.println("学生2:" + stu2.getNumber());  stu2.setNumber(54321);  System.out.println("学生1:" + stu1.getNumber());  System.out.println("学生2:" + stu2.getNumber());  }
}  

结果:

学生1:12345

学生2:12345

学生1:12345

学生2:54321

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

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

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

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

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

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

结果:

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

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

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

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

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

结果:

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

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

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

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

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

结果:

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

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

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

java.util.Date:

/** * Return a copy of this object. */
public Object clone() {  Date d = null;  try {  d = (Date)super.clone();  if (cdate != null) {  d.cdate = (BaseCalendar.Date) cdate.clone();  }  } catch (CloneNotSupportedException e) {} // Won't happen  return d;
}  

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

1、浅克隆

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

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

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

2、深克隆

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

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

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

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

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

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

解决多层克隆问题

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

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

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

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

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

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

总结

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

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

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

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

转载于:https://www.cnblogs.com/xiarongjin/p/8308917.html

Java clone() 浅拷贝 深拷贝相关推荐

  1. java clone 深拷贝_Java clone() 浅拷贝 深拷贝

    假如说你想复制一个简单变量.很简单: int apples = 5;int pears = apples; 不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,f ...

  2. 详解Java中的clone方法 -- 深拷贝和浅拷贝

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

  3. Java的浅拷贝与深拷贝总结

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

  4. Java中的深拷贝与浅拷贝浅析

    为什么需要拷贝?   拷贝的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的"状态"的时候,就需要clone( ...

  5. Clone方法与浅拷贝深拷贝

    Clone方法与浅拷贝&深拷贝 介绍 如何实现 浅拷贝 shallow copy 深拷贝 deep copy 数组中的clone 介绍 在Java中,如果需要创建一个对象的副本,特别是这个对象 ...

  6. Java中浅拷贝与深拷贝之间的区别

    在深入探讨Java中浅表副本与深表副本之间的差异之前,让我们看看首先进行克隆的是什么. 什么是克隆? 克隆是在内存中创建现有对象的精确副本的过程.在Java中,java.lang.Object类的cl ...

  7. Java 深入理解深拷贝和浅拷贝区别

    title: Java 深入理解深拷贝和浅拷贝区别 date: 2021-6-19 updated: 2021-6-19 tags: Java 深拷贝和浅拷贝 categories: 面试 Java ...

  8. Java中浅拷贝和深拷贝的区别

    深拷贝和浅拷贝的区别 浅拷贝:被拷贝的对象的所有属性值都与原来的对象相同,而对象的所有属性引用仍然指向原来的属性所指向的内存地址.需要注意的是cloneObj == obj 返回的是false,所以使 ...

  9. 基于java实现浅拷贝和深拷贝

    目录 1.概念 2.浅拷贝 2.1.浅拷贝实战 3.深拷贝 3.1.嵌套 clone 方法 3.2.使用序列化流 3.3.使用开源工具类 1.概念 浅拷贝:在拷贝一个对象时,复制基本数据类型的成员变量 ...

  10. java clone原理_详解Java中的clone方法 -- 原型模式

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

最新文章

  1. ORA-12514 TNS 监听程序当前无法识别连接描述符中请求服务-Oracle连接错误
  2. 解析Objective-C中多态、动态类型和动态绑定
  3. 步步为营-19-正则表达式的简单应用
  4. Fragment之一:Fragment入门
  5. left join 、right join 、inner join和 full join的区别
  6. Hello JavaFX 2.0:命令行介绍
  7. mysql判断数字的函数_Mysql必读MySql判断汉字、日期、数字的具体函数
  8. python数据库安装_python数据库-MySQL安装问题总结(48)
  9. VS2015+qt5.11入门(实现计算机的加法和登录操作)
  10. Z-Stack通过按键中断实现长按功能
  11. Jquery全选单选功能
  12. 十分钟释疑Oracle中“小表超慢”之谜(SQL调优/SQL优化)
  13. JavaSE中容易忽视的坑
  14. 百度地图根据缩放级别显示不同的marker点和窗口内容
  15. The simplest way to combine two csv file in python
  16. 指纹采集器测试软件,售完存档:小熊做的关于奔凯BIOCOME USB指纹采集器 指纹识别仪TCR4 Win7 Win8 Win10下的使用教程...
  17. 对比学习Contrastive Learning
  18. BZOJ 4605: 崂山白花蛇草水 树套树 权值线段树套kdtree
  19. 13、DA数模转换(PWM输出)
  20. LTspice基础教程-020.绘制伯德图

热门文章

  1. linux快捷删除151617文件,mengzhaoliang
  2. 基于sklearn分析特征工程(特征预处理、特征选择、降维)
  3. 登录服务器信息怎么删除,服务器端删除了用户,怎么通知客户端浏览器删除cookie...
  4. python生活中的小问题_python日常注意小知识集锦
  5. python编码转换语句_将“ yield from”语句转换为Python 2.7代码
  6. a^x ≡1(mod n) Ord_n(a)=x什么意思
  7. chainmaker-go-sdk get cert hash failed, get cert hash failed, send QUERY_SYSTEM_CONTRACT failed
  8. 当前读和快照读是什么 区别
  9. 【Django 2021年最新版教程4】为项目添加资源文件(css,js,image)
  10. 网站页面直接显示html代码 转义html代码 excel导入题库 解决方法