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

int apples = 5;int pears = apples;

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

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

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

classStudent {private intnumber;public intgetNumber() {returnnumber;

}public void setNumber(intnumber) {this.number =number;

}

}public classTest {public static voidmain(String args[]) {

Student stu1= newStudent();

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 implementsCloneable{private intnumber;public intgetNumber() {returnnumber;

}public void setNumber(intnumber) {this.number =number;

}

@OverridepublicObject clone() {

Student stu= null;try{

stu= (Student)super.clone();

}catch(CloneNotSupportedException e) {

e.printStackTrace();

}returnstu;

}

}public classTest {public static voidmain(String args[]) {

Student stu1= newStudent();

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类。

classAddress {2 privateString add;3

4 publicString getAdd() {5 returnadd;6}7

8 public voidsetAdd(String add) {9 this.add =add;10}11

12}13

14 class Student implementsCloneable{15 private intnumber;16

17 privateAddress addr;18

19 publicAddress getAddr() {20 returnaddr;21}22

23 public voidsetAddr(Address addr) {24 this.addr =addr;25}26

27 public intgetNumber() {28 returnnumber;29}30

31 public void setNumber(intnumber) {32 this.number =number;33}34

35@Override36 publicObject clone() {37 Student stu = null;38 try{39 stu = (Student)super.clone();40 }catch(CloneNotSupportedException e) {41e.printStackTrace();42}43 returnstu;44}45}46 public classTest {47

48 public static voidmain(String args[]) {49

50 Address addr = newAddress();51 addr.setAdd("杭州市");52 Student stu1 = newStudent();53 stu1.setNumber(123);54stu1.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方法,完整代码如下:

packageabc;2

3 class Address implementsCloneable {4 privateString add;5

6 publicString getAdd() {7 returnadd;8}9

10 public voidsetAdd(String add) {11 this.add =add;12}13

14@Override15 publicObject clone() {16 Address addr = null;17 try{18 addr = (Address)super.clone();19 }catch(CloneNotSupportedException e) {20e.printStackTrace();21}22 returnaddr;23}24}25

26 class Student implementsCloneable{27 private intnumber;28

29 privateAddress addr;30

31 publicAddress getAddr() {32 returnaddr;33}34

35 public voidsetAddr(Address addr) {36 this.addr =addr;37}38

39 public intgetNumber() {40 returnnumber;41}42

43 public void setNumber(intnumber) {44 this.number =number;45}46

47@Override48 publicObject clone() {49 Student stu = null;50 try{51 stu = (Student)super.clone(); //浅复制

52 }catch(CloneNotSupportedException e) {53e.printStackTrace();54}55 stu.addr = (Address)addr.clone(); //深度复制

56 returnstu;57}58}59 public classTest {60

61 public static voidmain(String args[]) {62

63 Address addr = newAddress();64 addr.setAdd("杭州市");65 Student stu1 = newStudent();66 stu1.setNumber(123);67stu1.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.*/

publicObject clone() {

Date d= null;try{

d= (Date)super.clone();if (cdate != null) {

d.cdate=(BaseCalendar.Date) cdate.clone();

}

}catch (CloneNotSupportedException e) {} //Won't happen

returnd;

}

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

1、浅克隆

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

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

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

2、深克隆

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

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

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

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

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

扩展

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

解决多层克隆问题

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

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

public class Outer implementsSerializable{2 private static final long serialVersionUID = 369285298572941L; //最好是显式声明ID

3 publicInner inner;4  //Discription:[深度复制方法,需要对象及对象所有的对象属性都实现序列化]

5 publicOuter myclone() {6 Outer outer = null;7 try { //将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝

8 ByteArrayOutputStream baos = newByteArrayOutputStream();9 ObjectOutputStream oos = newObjectOutputStream(baos);10 oos.writeObject(this);11       //将流序列化成对象

12 ByteArrayInputStream bais = newByteArrayInputStream(baos.toByteArray());13 ObjectInputStream ois = newObjectInputStream(bais);14 outer =(Outer) ois.readObject();15 } catch(IOException e) {16e.printStackTrace();17 } catch(ClassNotFoundException e) {18e.printStackTrace();19}20 returnouter;21}22 }

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

public class Inner implementsSerializable{2 private static final long serialVersionUID = 872390113109L; //最好是显式声明ID

3 public String name = "";4

5 publicInner(String name) {6 this.name =name;7}8

9@Override10 publicString toString() {11 return "Inner的name值为:" +name;12}13 }

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

总结

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

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

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

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

java clone 深拷贝_Java clone() 浅拷贝 深拷贝相关推荐

  1. Java 对象的克隆Clone和必须了解的浅拷贝与深拷贝

    前言 为什么要写这篇文章? 因为我最近无意间看到了一些项目的代码,不管是曾经编码的人还是新接手的人, 在想完全克隆复制一个已经存在的对象,做为后续的使用,竟然都是采取了重写new一个,然后一个个属性字 ...

  2. 【java】克隆(clone),浅拷贝和深拷贝的区别

    前言 在很多人初学的时候,一定会被克隆是什么?怎么使用克隆?浅拷贝和深拷贝又是啥?所搞得晕头转向.这里我希望用最简单的语言向大家介绍我自己的理解.(我们马克思老师说,你能不能用农民也听得懂的语言介绍一 ...

  3. 深拷贝(deep clone)与浅拷贝(shallow clone)

    深拷贝(deep clone)与浅拷贝(shallow clone) 浅复制(浅克隆):被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅复制仅仅复 ...

  4. java 深拷贝_java 深拷贝与浅拷贝机制详解

    java 深拷贝与浅拷贝机制详解 概要: 在Java中,拷贝分为深拷贝和浅拷贝两种.java在公共超类Object中实现了一种叫做clone的方法,这种方法clone出来的新对象为浅拷贝,而通过自己定 ...

  5. java拷贝函数_Java的深拷贝与浅拷贝的几种实现方式

    1.介绍 关于Java的深拷贝和浅拷贝,简单来说就是创建一个和已知对象一模一样的对象.可能日常编码过程中用的不多,但是这是一个面试经常会问的问题,而且了解深拷贝和浅拷贝的原理,对于Java中的所谓值传 ...

  6. java 反射 深拷贝_Java深拷贝和浅拷贝(深克隆和浅克隆)

    Java中创建对象有两种方式: 通过new操作符创建一个对象 通过clone方法来复制一个对象 使用反序列化来创建一个对象 通过使用Class类的newInstance方法来创建一个对象 使用Cons ...

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

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

  8. java数组深拷贝和浅拷贝_java中的深拷贝与浅拷贝(值类型 vs 引用类型)

    对象赋值 赋值是日常编程过程中最常见的操作,最简单的比如: Student codeSheep = new Student(); Student codePig = codeSheep; 严格来说,这 ...

  9. Java中克隆的用法,深拷贝、浅拷贝概念的引出

    一 什么是克隆 克隆就是根据已有对象复制出另一个对象.比如用A克隆出B,一般在java中有如下约定: // A与B的引用不同 A!=B // A与B的类相同 A.getClass == B.getCl ...

最新文章

  1. 不止狂卖芯片,英伟达也飙了新算法:用AI生成逼真的虚假名人照,可用于万物
  2. 坚持不懈续集 (二) 初学者挑战学习Python编程30天
  3. 关于设置GridControl属性在代码中的顺序带来的不同效果
  4. python常用模块大全总结-python常用模块整理
  5. Activity生命周期的补充
  6. 【高清截图】UbuntuKylin 14.04 桌面版安装步骤
  7. iOS开发多线程-RunLoop
  8. maven安装后环境变量配置
  9. C# 弹出输入框取值
  10. mysql my.ini配置优化_求高手帮我优化一下 mysql 的 my.ini
  11. VM14 安装黑苹果教程步骤
  12. 删除win10添加的网络位置
  13. 在Ubuntu 12.10中安装QQ2012
  14. Sources for ‘Android API 32 Platform’ not found
  15. 蒲公英企服平台对话核盛网络刘涛——Tracup项目管理的一条生命线
  16. Win10 通过重置来修复系统的方法
  17. 【HTML】【消失的花木兰】花木兰:三兔蹦迪走,安能辨我是兔子?
  18. 如何得到每个区域的每个土地利用类型的面积
  19. Three Character Classic 三字经
  20. 亚马逊AWS免费EC2服务器搭建总结

热门文章

  1. 三大框架ssh整合(一)
  2. iOS 快捷下载和安装并使用CocoaPods
  3. Welcome-to-Swift-05控制流(Control Flow )
  4. LintCode: Two Sum
  5. 【原创】中文分词系统 ICTCLAS2015 的JAVA封装和多线程执行(附代码)
  6. 我要好offer之 二叉树大总结
  7. 【ArcGIS 10.2新特性】Portal for ArcGIS新特性
  8. 什么是BI(Business Intelligence
  9. 整合TextBox与Label 创建新控件--EFLabelText
  10. IE6 的 hover 伪类 bug