Java clone() 浅拷贝 深拷贝
假如说你想复制一个简单变量。很简单:
- 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虚拟机上面的,要想访问到比较底层的与操作系统相关的就没办法了,只能由靠近操作系统的语言来实现。
- 第一次声明保证克隆对象将有单独的内存地址分配。
- 第二次声明表明,原始和克隆的对象应该具有相同的类类型,但它不是强制性的。
- 第三声明表明,原始和克隆的对象应该是平等的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方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。
- public class Outer implements Serializable{
- 2 private static final long serialVersionUID = 369285298572941L; //最好是显式声明ID
- 3 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; //最好是显式声明ID
- 3 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方法克隆对象。让问题在编译的时候暴露出来总是优于把问题留到运行时。
Java clone() 浅拷贝 深拷贝的更多相关文章
- Java clone() 方法克隆对象——深拷贝与浅拷贝
基本数据类型引用数据类型特点 1.基本数据类型的特点:直接存储在栈(stack)中的数据 2.引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里 引用数据类型在栈中存储了指针,该指 ...
- 深入理解Java中的Clone与深拷贝和浅拷贝
1.Java对象的创建 clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象. ...
- Java clone克隆方法 --深拷贝--浅拷贝 --原型模型
什么是深拷贝? 什么是浅拷贝? 创建一个对象的方法有几种? 默认的Object方法中的clone是深拷贝还是浅拷贝? 为什么说很多深拷贝都是不彻底的深拷贝? 什么是原型模型,什么是原型模式? 原型模型 ...
- 浅谈Java中的深拷贝和浅拷贝(转载)
浅谈Java中的深拷贝和浅拷贝(转载) 原文链接: http://blog.csdn.net/tounaobun/article/details/8491392 假如说你想复制一个简单变量.很简单: ...
- 浅谈Java中的深拷贝和浅拷贝
转载: 浅谈Java中的深拷贝和浅拷贝 假如说你想复制一个简单变量.很简单: int apples = 5; int pears = apples; 不仅仅是int类型,其它七种原始数据类型(bool ...
- 内功心法 -- Java中的深拷贝和浅拷贝
写在前面的话:读书破万卷,编码如有神--------------------------------------------------------------------这篇博客主要来谈谈" ...
- Java之浅拷贝与深拷贝
----?浅拷贝 --- 概念 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.简单说,浅拷贝就是只复制所考虑的对象,而不复制它所引用的对象 --- 实现方 ...
- Java基础(十三)--深拷贝和浅拷贝
在上篇文章:Java基础(十二)--clone()方法,我们简单介绍了clone()的使用 clone()对于基本数据类型的拷贝是完全没问题的,但是如果是引用数据类型呢? @Data @NoArgsC ...
- java中的深拷贝与浅拷贝
Java中对象的创建 clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象.那 ...
随机推荐
- laravel5.5源码笔记(二、服务提供者provider)
laravel里所谓的provider服务提供者,其实是对某一类功能进行整合,与做一些使用前的初始化引导工作.laravel里的服务提供者也分为,系统核心服务提供者.与一般系统服务提供者.例如上一篇博 ...
- IC设计笔试面试小问题总结(随时更新)-IC设计笔记(三)
都是一些细节性问题,放在一起记忆,一问一答的形式,有任何问题欢迎文章上方微博讨论,多思多想. 1.What makes the difference between Run time and CPU ...
- Machine Learning——吴恩达机器学习笔记(酷
[1] ML Introduction a. supervised learning & unsupervised learning 监督学习:从给定的训练数据集中学习出一个函数(模型参数), ...
- Egret 菜鸟级使用手册--第二天
################新的一天,我还是大佬 今天加载个英雄,先在GameScene里搞一个英雄出来,然后再创建一个Hreo类 接下来又一个新的API egret.TouchEvent.TOU ...
- Linux入门进阶第三天——软件安装管理(下)
一.yum在线安装 之前的rpm包各种依赖性太强!安装复杂,yum的好处就来了: // yum 在redhat是付费服务 1.yum源文件 先进入到yum目录: 我们打开默认生效的Base包 2.光盘 ...
- 2017-2018-1 20155321《信息安全技术》实验二——Windows口令破解
2017-2018-1 20155321<信息安全技术>实验二--Windows口令破解 实验原理 口令破解方法 口令破解主要有两种方法:字典破解和暴力破解. 字典破解是指通过破解者对管理 ...
- 20155329胡佩伦《Java程序设计》第2周学习总结
学号 20155329 <Java程序设计>第2周学习总结 教材学习内容总结 基本类型 整数(short.int.long) 字节(byte) 浮点数(float/double) 字符(c ...
- addClass+siblings+removeClass用意:
$(this).addClass("li_add").siblings().removeClass("li_add").children('.floor2'). ...
- HBase核心功能模块--读书笔记
客户端Client 客户端 Client 是整个 HBase 系统的入口.使用者直接通过客户端操作 HBase.客户端 使用 HBase 的 RPC 机制与 HMaster 和 RegionServe ...
- JS基础循环语句练习
最近这几天一直在做题做题做题,很恶心,很头疼,都快吐了,但是自己的逻辑还是初见成效的,很久不动脑了,有点锈住了,大家也一起来开发下自己的大脑 有关简单排序的小循环 <script> var ...