`
xpp02
  • 浏览: 1014332 次
社区版块
存档分类
最新评论

我不知道的事——深克隆和浅克隆

 
阅读更多



推荐一部好电影《致命魔术》。(此处为植入广告)
推荐理由:涉及人性。画面不错,剧情跌宕,亦魔亦幻(此处的”魔“为魔术的”魔“)。虽然女猪脚不尽如人意,但是男猪脚比较帅。而且看完后有利于理解克隆,当然理解了克隆也利于观影!

首先,简单客观地解释下几个关键的名词(我们约定A表示原来的对象,P表示A引用的对象;AC表示克隆后的A对象):
浅克隆:复制克隆对象的基本信息及其对其他对象的引用。在改变AC对象的P对象时,那么也会改变A对象的P对象。
深克隆:深克隆也会复制对象的基本信息以及其对其他对象的引用,但是,改变AC对象的引用P对象时,不会引起A对象的P对象。

从前面浅克隆的定义上看,改变AC的P就能改变A的P,这样显得这种克隆更加像深克隆(都刨到别人祖坟了,够深的!)。但是,换个角度来看,这种克隆只是浅显的将一个对象拷贝出来了,并没有真正的去对这个对象进行深入地剖析,即没有剥离两者之间的依赖,使得A和AC更像一个对象的不同命名,因此,反而显得浅显了。深克隆的技术含量也较之浅克隆高点。
为了方便理解,我将浅克隆形象化为一对连体双胞胎,而将深克隆形象化为一对同卵双胞胎;或者也可将浅克隆理解为镜像,而深克隆则是复制了一个真正具有独立行为能力的实体。
下面详细对它们进行阐述:
克隆
实现克隆的类都必须实现Cloneable接口,而且一般需要重写Object类里的clone()方法。我们首先看看Object类中对clone()方法的注释与声明:

Java代码 收藏代码
  1. /**
  2. *Createsandreturnsacopyofthisobject.Theprecisemeaning
  3. *of"copy"maydependontheclassoftheobject.Thegeneral
  4. *intentisthat,foranyobject{@codex},theexpression:
  5. *<blockquote>
  6. *<pre>
  7. *x.clone()!=x</pre></blockquote>
  8. *willbetrue,andthattheexpression:
  9. *<blockquote>
  10. *<pre>
  11. *x.clone().getClass()==x.getClass()</pre></blockquote>
  12. *willbe{@codetrue},butthesearenotabsoluterequirements.
  13. *Whileitistypicallythecasethat:
  14. *<blockquote>
  15. *<pre>
  16. *x.clone().equals(x)</pre></blockquote>
  17. *willbe{@codetrue},thisisnotanabsoluterequirement.
  18. *<p>
  19. *Byconvention,thereturnedobjectshouldbeobtainedbycalling
  20. *{@codesuper.clone}.Ifaclassandallofitssuperclasses(except
  21. *{@codeObject})obeythisconvention,itwillbethecasethat
  22. *{@codex.clone().getClass()==x.getClass()}.
  23. *<p>
  24. *Byconvention,theobjectreturnedbythismethodshouldbeindependent
  25. *ofthisobject(whichisbeingcloned).Toachievethisindependence,
  26. *itmaybenecessarytomodifyoneormorefieldsoftheobjectreturned
  27. *by{@codesuper.clone}beforereturningit.Typically,thismeans
  28. *copyinganymutableobjectsthatcomprisetheinternal"deepstructure"
  29. *oftheobjectbeingclonedandreplacingthereferencestothese
  30. *objectswithreferencestothecopies.Ifaclasscontainsonly
  31. *primitivefieldsorreferencestoimmutableobjects,thenitisusually
  32. *thecasethatnofieldsintheobjectreturnedby{@codesuper.clone}
  33. *needtobemodified.
  34. *<p>
  35. *Themethod{@codeclone}forclass{@codeObject}performsa
  36. *specificcloningoperation.First,iftheclassofthisobjectdoes
  37. *notimplementtheinterface{@codeCloneable},thena
  38. *{@codeCloneNotSupportedException}isthrown.Notethatallarrays
  39. *areconsideredtoimplementtheinterface{@codeCloneable}andthat
  40. *thereturntypeofthe{@codeclone}methodofanarraytype{@codeT[]}
  41. *is{@codeT[]}whereTisanyreferenceorprimitivetype.
  42. *Otherwise,thismethodcreatesanewinstanceoftheclassofthis
  43. *objectandinitializesallitsfieldswithexactlythecontentsof
  44. *thecorrespondingfieldsofthisobject,asifbyassignment;the
  45. *contentsofthefieldsarenotthemselvescloned.Thus,thismethod
  46. *performsa"shallowcopy"ofthisobject,nota"deepcopy"operation.
  47. *<p>
  48. *Theclass{@codeObject}doesnotitselfimplementtheinterface
  49. *{@codeCloneable},socallingthe{@codeclone}methodonanobject
  50. *whoseclassis{@codeObject}willresultinthrowingan
  51. *exceptionatruntime.
  52. *
  53. *@returnacloneofthisinstance.
  54. *@exceptionCloneNotSupportedExceptioniftheobject'sclassdoesnot
  55. *supportthe{@codeCloneable}interface.Subclasses
  56. *thatoverridethe{@codeclone}methodcanalso
  57. *throwthisexceptiontoindicatethataninstancecannot
  58. *becloned.
  59. *@seejava.lang.Cloneable
  60. */
  61. protectednativeObjectclone()throwsCloneNotSupportedException;

虽然过长,但是我觉得还是很有必要看看的。从前面的注释中可以看出:x.clone() != x 但是 x.clone().getClass() == x.getClass() 。这可以看成克隆的精确描述。从x.clone() != x 看,觉得这个镜像也不简单,镜子里面的世界和镜子外面的世界原来也不是同一个,开始有一点魔幻的味道了。注释里还有一句话值得我们关注:Note that all arrays are considered to implement the interface Cloneable and that the return type of the clone method of an array type T[] is T[] where T is any reference or primitive type.所有的数组都实现了Cloneable接口,返回的是一个数组类型。这个大家可以验证一下,反正我验证是有的。这段注释里还有很多地方值得我们去研究(比如提到了深克隆和浅克隆),我都好不容易贴出来了,大家自己去看看吧!
clone()方法会抛出CloneNotSupportedException,这是为什么呢?这是因为Object类没有实现Cloneable接口。身为万物之祖,Object也有很多不会的啊!

浅克隆
要想做到AC的属性和A一样其实并不难,最简单的办法就是AC = A;而且也能保证改变AC的P会引起A的P改变。这样不就可以了吗?为什么还要用克隆呢?你似乎忘了,在克隆里我们讲过,AC和A需满足两个条件:x.clone() != x和x.clone().getClass() == x.getClass()。如果直接AC = A,很明显AC == A返回的是true。至于具体原因就涉及到克隆的作用了,等会的克隆的用途会详细说明。
浅克隆的实现并不难,下面看一个示例:

Java代码 收藏代码
  1. classSword{
  2. Stringname;
  3. floatweight;
  4. publicSword(Stringname,floatweight){
  5. this.name=name;
  6. this.weight=weight;
  7. }//endconstructor
  8. }//endclassSword
  9. classHeroimplementsCloneable{
  10. Stringname;
  11. intenergy;//hero的战斗值
  12. Swords;
  13. publicHero(Stringname,intenergy,Swords){
  14. this.name=name;
  15. this.energy=energy;
  16. this.s=s;
  17. }//endconstructor
  18. publicvoidkill(){
  19. System.out.println("战斗值为"+energy+"的"+name+"挥动着重为"
  20. +s.weight+"斤的"+s.name+"要开杀戒了!");
  21. }//endkill
  22. /**
  23. *重写Object的clone方法。
  24. */
  25. publicObjectclone(){
  26. Heroh=null;
  27. try{
  28. h=(Hero)super.clone();
  29. }catch(CloneNotSupportedExceptione){
  30. e.printStackTrace();
  31. }//endtry-catch
  32. returnh;
  33. }//endclone
  34. }//endclassHero
  35. publicclassShallowClone{
  36. /**
  37. *主函数。
  38. *@paramargs
  39. */
  40. publicstaticvoidmain(String[]args){
  41. //声明一个Sword对象
  42. Swords=newSword("绝世好剑",58.3f);
  43. //声明一个Hero
  44. Heroh1=newHero("步惊云",1000,s);
  45. h1.kill();
  46. //克隆
  47. Heroh2=(Hero)h1.clone();
  48. //改变h2的s的一些属性
  49. h2.s.name="草雉剑";
  50. h2.s.weight=23.4f;
  51. h1.kill();
  52. if(!(h1==h2)){
  53. System.out.println("从哲学的角度讲:此时的"+
  54. h1.name+"已经不是从前的"+h1.name+"了!");
  55. }else{
  56. System.out.println("娃哈哈,我"+h1.name+"还是"+h1.name+"!");
  57. }//endif-else
  58. }//endmain
  59. }//endclassShallowClone

这段代码的运行结果是什么呢?请看:
战斗值为1000的步惊云挥动着重为58.3斤的绝世好剑要开杀戒了!
战斗值为1000的步惊云挥动着重为23.4斤的草雉剑要开杀戒了!
从哲学的角度讲:此时的步惊云已经不是从前的步惊云了!
是的,正如我们所说的h1的s对象的name和weight也改变了。而且其实现也是很简单。当然对这一块比较熟悉的朋友会非常气愤地指出,这里有一些基本的常识错误:绝世好剑和草雉剑根本就不是这个重量,步惊云也得不到草雉剑!但是,("made in China".equals("everything is possible")) == true(支持国产,再次植入广告!)。好吧,我们回到浅克隆,这里实现浅克隆的代码相当简单,直接super.clone()就可以了。
网上有一种说法,说浅克隆是不正确的克隆。我觉得不管正不正确,当我们要克隆的对象只有基本数据类型和String等属性时,直接浅克隆就可以了。运用之妙,存乎一心!

深克隆
前面讲了,深克隆就是将克隆的对象和原来的对象独立开来。那么怎么实现呢?
在上面的代码上修改了一点:

Java代码 收藏代码
  1. classSwordimplementsCloneable{
  2. Stringname;
  3. floatweight;
  4. publicSword(Stringname,floatweight){
  5. this.name=name;
  6. this.weight=weight;
  7. }//endconstructor
  8. publicObjectclone(){
  9. try{
  10. returnsuper.clone();
  11. }catch(CloneNotSupportedExceptione){
  12. e.printStackTrace();
  13. }//endtry-catch
  14. returnnull;
  15. }//endclone
  16. }//endclassSword
  17. classHeroimplementsCloneable{
  18. Stringname;
  19. intenergy;//hero的战斗值
  20. Swords;
  21. publicHero(Stringname,intenergy,Swords){
  22. this.name=name;
  23. this.energy=energy;
  24. this.s=s;
  25. }//endconstructor
  26. publicvoidkill(){
  27. System.out.println("战斗值为"+energy+"的"+name+"挥动着重为"+s.weight+"斤的"+s.name+"要开杀戒了!");
  28. }//endkill
  29. /**
  30. *重写Object的clone方法。
  31. */
  32. publicObjectclone(){
  33. Heroh=null;
  34. try{
  35. h=(Hero)super.clone();
  36. h.s=(Sword)s.clone();
  37. }catch(CloneNotSupportedExceptione){
  38. e.printStackTrace();
  39. }//endtry-catch
  40. returnh;
  41. }//endclone
  42. }//endclassHero
  43. publicclassDeepClone{
  44. /**
  45. *主函数。
  46. *@paramargs
  47. */
  48. publicstaticvoidmain(String[]args){
  49. //声明一个Sword对象
  50. Swords=newSword("绝世好剑",58.3f);
  51. //声明一个Hero
  52. Heroh1=newHero("步惊云",1000,s);
  53. h1.kill();
  54. //克隆
  55. Heroh2=(Hero)h1.clone();
  56. //改变h2的s的一些属性
  57. h2.s.name="草雉剑";
  58. h2.s.weight=23.4f;
  59. h1.kill();
  60. if(!(h1==h2)){
  61. System.out.println("从哲学的角度讲:此时的"+
  62. h1.name+"已经不是从前的"+h1.name+"了!");
  63. }else{
  64. System.out.println("娃哈哈,我"+h1.name+"还是"+h1.name+"!");
  65. }//endif-else
  66. }//endmain
  67. }//endclassDeepClone

认真观察就会发现,代码的变动并不是很大,只是Sword类也实现了Cloneable接口,在Hero中也对hero对象的sword进行了克隆。这样就实现了深克隆。那么这段代码的结果是不是我们希望看到的呢:
战斗值为1000的步惊云挥动着重为58.3斤的绝世好剑要开杀戒了!
战斗值为1000的步惊云挥动着重为58.3斤的绝世好剑要开杀戒了!
从哲学的角度讲:此时的步惊云已经不是从前的步惊云了!
看吧,h1并没有因为克隆后的h2改变了s的name和weight而跟着发生了改变,圆满完成了我们的预期目标。
关于深克隆还有另一种方式:使用Serializable。大家可以去关注一下,这里就不讨论了。

克隆的用途
我们知道了深克隆和浅克隆,那么克隆到底有什么用呢?
答案很简单:有需求就有市场。我们要克隆是因为我们需要一个和已知对象一样的对象(这个我觉得看了《致命魔术》后肯定理解得更深)。当我们需要一个对象的副本但又不想影响原来的对象时,我们可以考虑使用克隆。
个人觉得克隆为程序员提供了对对象更加灵活的操纵力。我觉得大家在理解的基础上然后提出自己的见解就可以了。

总结
最近看《Effective Java》,里面专门提到了:谨慎地覆盖clone。而且里面也提到了用copy constructor(克隆构造器)或者copy factory(克隆工厂)更加地安全。网上有很多解说的,但是我觉得这个版本不错,大家去看看吧:http://www.slideshare.net/fmshaon/effective-java-override-clone-method-judiciously

最后,还有一件事,《致命魔术》真的不错!
晚安!

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics