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



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

       首先,简单客观地解释下几个关键的名词(我们约定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.      * Creates and returns a copy of this object.  The precise meaning 
  3.      * of "copy" may depend on the class of the object. The general 
  4.      * intent is that, for any object {@code x}, the expression: 
  5.      * <blockquote> 
  6.      * <pre> 
  7.      * x.clone() != x</pre></blockquote> 
  8.      * will be true, and that the expression: 
  9.      * <blockquote> 
  10.      * <pre> 
  11.      * x.clone().getClass() == x.getClass()</pre></blockquote> 
  12.      * will be {@code true}, but these are not absolute requirements. 
  13.      * While it is typically the case that: 
  14.      * <blockquote> 
  15.      * <pre> 
  16.      * x.clone().equals(x)</pre></blockquote> 
  17.      * will be {@code true}, this is not an absolute requirement. 
  18.      * <p> 
  19.      * By convention, the returned object should be obtained by calling 
  20.      * {@code super.clone}.  If a class and all of its superclasses (except 
  21.      * {@code Object}) obey this convention, it will be the case that 
  22.      * {@code x.clone().getClass() == x.getClass()}. 
  23.      * <p> 
  24.      * By convention, the object returned by this method should be independent 
  25.      * of this object (which is being cloned).  To achieve this independence, 
  26.      * it may be necessary to modify one or more fields of the object returned 
  27.      * by {@code super.clone} before returning it.  Typically, this means 
  28.      * copying any mutable objects that comprise the internal "deep structure" 
  29.      * of the object being cloned and replacing the references to these 
  30.      * objects with references to the copies.  If a class contains only 
  31.      * primitive fields or references to immutable objects, then it is usually 
  32.      * the case that no fields in the object returned by {@code super.clone} 
  33.      * need to be modified. 
  34.      * <p> 
  35.      * The method {@code clone} for class {@code Object} performs a 
  36.      * specific cloning operation. First, if the class of this object does 
  37.      * not implement the interface {@code Cloneable}, then a 
  38.      * {@code CloneNotSupportedException} is thrown. Note that all arrays 
  39.      * are considered to implement the interface {@code Cloneable} and that 
  40.      * the return type of the {@code clone} method of an array type {@code T[]} 
  41.      * is {@code T[]} where T is any reference or primitive type. 
  42.      * Otherwise, this method creates a new instance of the class of this 
  43.      * object and initializes all its fields with exactly the contents of 
  44.      * the corresponding fields of this object, as if by assignment; the 
  45.      * contents of the fields are not themselves cloned. Thus, this method 
  46.      * performs a "shallow copy" of this object, not a "deep copy" operation. 
  47.      * <p> 
  48.      * The class {@code Object} does not itself implement the interface 
  49.      * {@code Cloneable}, so calling the {@code clone} method on an object 
  50.      * whose class is {@code Object} will result in throwing an 
  51.      * exception at run time. 
  52.      * 
  53.      * @return     a clone of this instance. 
  54.      * @exception  CloneNotSupportedException  if the object's class does not 
  55.      *               support the {@code Cloneable} interface. Subclasses 
  56.      *               that override the {@code clone} method can also 
  57.      *               throw this exception to indicate that an instance cannot 
  58.      *               be cloned. 
  59.      * @see java.lang.Cloneable 
  60.      */  
  61.     protected native Object clone() throws CloneNotSupportedException;  

       虽然过长,但是我觉得还是很有必要看看的。从前面的注释中可以看出: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. class Sword{  
  2.         String name;  
  3.         float weight;  
  4.         public Sword(String name, float weight){  
  5.             this.name = name;  
  6.             this.weight = weight;  
  7.         } // end constructor  
  8.     } // end class Sword  
  9.       
  10.     class Hero implements Cloneable{  
  11.         String name;  
  12.         int energy; // hero的战斗值  
  13.         Sword s;  
  14.         public Hero(String name, int energy, Sword s){  
  15.             this.name = name;  
  16.             this.energy = energy;  
  17.             this.s = s;  
  18.         } // end constructor  
  19.           
  20.         public void kill(){  
  21.             System.out.println("战斗值为" + energy + "的" + name + "挥动着重为"  
  22.                     + s.weight + "斤的" + s.name + "要开杀戒了!");  
  23.         } // end kill  
  24.           
  25.         /** 
  26.          * 重写Object的clone方法。 
  27.          */  
  28.         public Object clone(){  
  29.             Hero h = null;  
  30.             try {  
  31.                 h = (Hero)super.clone();  
  32.             } catch (CloneNotSupportedException e) {  
  33.                 e.printStackTrace();  
  34.             } // end try-catch  
  35.             return h;  
  36.         } // end clone  
  37.     } // end class Hero  
  38.       
  39.     public class ShallowClone{  
  40.         /** 
  41.          * 主函数。 
  42.          * @param args 
  43.          */  
  44.         public static void main(String[] args) {  
  45.             // 声明一个Sword对象  
  46.             Sword s = new Sword("绝世好剑"58.3f);  
  47.             // 声明一个Hero  
  48.             Hero h1 = new Hero("步惊云"1000, s);  
  49.             h1.kill();  
  50.             // 克隆  
  51.             Hero h2 = (Hero) h1.clone();  
  52.             // 改变h2的s的一些属性  
  53.             h2.s.name = "草雉剑";  
  54.             h2.s.weight = 23.4f;  
  55.             h1.kill();  
  56.         if( !(h1 == h2)){  
  57.             System.out.println("从哲学的角度讲:此时的" +   
  58.                 h1.name + "已经不是从前的" + h1.name + "了!");  
  59.         }else{  
  60.             System.out.println("娃哈哈,我" + h1.name + "还是" + h1.name                   + "!");  
  61.             } // end if-else  
  62.         } // end main     
  63.     } // end class ShallowClone  

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

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

Java代码  收藏代码
  1. class Sword implements Cloneable{  
  2.         String name;  
  3.         float weight;  
  4.         public Sword(String name, float weight){  
  5.             this.name = name;  
  6.             this.weight = weight;  
  7.         } // end constructor  
  8.         public Object clone(){  
  9.             try {  
  10.                 return super.clone();  
  11.             } catch (CloneNotSupportedException e) {  
  12.                 e.printStackTrace();  
  13.             } // end try-catch  
  14.             return null;  
  15.         } // end clone  
  16.     } // end class Sword  
  17.   
  18.     class Hero implements Cloneable{  
  19.         String name;  
  20.         int energy; // hero的战斗值  
  21.         Sword s;  
  22.         public Hero(String name, int energy, Sword s){  
  23.             this.name = name;  
  24.             this.energy = energy;  
  25.             this.s = s;  
  26.         } // end constructor  
  27.         public void kill(){  
  28.             System.out.println("战斗值为" + energy + "的" + name + "挥动着                  重为" + s.weight + "斤的" + s.name + "要开杀戒了!");  
  29.         } // end kill  
  30.         /** 
  31.          * 重写Object的clone方法。 
  32.          */  
  33.         public Object clone(){  
  34.             Hero h = null;  
  35.             try {  
  36.                 h = (Hero)super.clone();  
  37.                 h.s = (Sword) s.clone();  
  38.             } catch (CloneNotSupportedException e) {  
  39.                 e.printStackTrace();  
  40.             } // end try-catch  
  41.             return h;  
  42.         } // end clone  
  43.     } // end class Hero  
  44.   
  45.     public class DeepClone{   
  46.         /** 
  47.          * 主函数。 
  48.         * @param args 
  49.         */  
  50.         public static void main(String[] args) {  
  51.             // 声明一个Sword对象  
  52.             Sword s = new Sword("绝世好剑"58.3f);  
  53.             // 声明一个Hero  
  54.             Hero h1 = new Hero("步惊云"1000, s);  
  55.             h1.kill();  
  56.             // 克隆  
  57.             Hero h2 = (Hero) h1.clone();  
  58.             // 改变h2的s的一些属性  
  59.             h2.s.name = "草雉剑";  
  60.             h2.s.weight = 23.4f;  
  61.             h1.kill();  
  62.             if(! (h1 == h2)){  
  63.                 System.out.println("从哲学的角度讲:此时的" +   
  64.                         h1.name + "已经不是从前的" + h1.name + "了!");  
  65.             }else{  
  66.                 System.out.println("娃哈哈,我" + h1.name + "还是" + h1.name + "!");  
  67.             } // end if-else  
  68.         } // end main  
  69.     } // end class DeepClone  

       认真观察就会发现,代码的变动并不是很大,只是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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/451590.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

服务器搭建

2019独角兽企业重金招聘Python工程师标准>>> 最近弄了个阿里云的服务器&#xff0c;想在上面搞点东西&#xff0c;故要搭建一套环境。登录linux&#xff0c;在终端输入 uname -a 即列出linux的内核版本号。&#xff08;服务器网址&#xff1a;http://101.132.235.56…

mybatis内部类映射写法

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. B类是A类中的内部类。 2.  映射写法&#xff1a; com.XXX.A$B , 注意A类和B类之间用 $ 表示内外关系&#xff0c;而不是常用的 …

Linux bash总结(一) 基础部分(适合初学者学习和非初学者参考)

第一部分 bash简介 —— 对bash进行简要介绍 第二部分 bash示例和书写流程 —— 以一个简单的bash为例&#xff0c;说明书写、执行bash的流程 第三部分 bash基础语法 —— 本章内容比较多&#xff0c;主要介绍if...else...条件判断&#xff0c;for循环等等。对于有编…

解决报错:java.util.UnknownFormatConversionException: Conversion = ‘p‘

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. ssm框架下 报错如题 2. 错误原因&#xff1a;我的情况是&#xff0c;代码中实体属性映射书写和数据库字段名字不一致。 选中位置数据…

TOAD连接Oracle数据库失败:OCI_INVALID_HANDLE解决

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. toad 连接Oracle数据库连接失败如图&#xff1a; 2. 导致这个情况的前因&#xff1a;toad运行情况下&#xff0c;突然断电。 3. 解决…

python-访问者模式

源码地址:https://github.com/weilanhanf/PythonDesignPatterns 说明&#xff1a; 访问者模式的基本想法是&#xff0c;软件系统中拥有一个由许多对象构成的、比较稳定的对象结构&#xff0c;这些对象的类都拥有一个 accept 方法用来接受访问者对象的访问。访问者是一个接口&am…

git commit -m和git commit -am

字面解释的话&#xff0c;git commit -m用于提交暂存区的文件&#xff1b;git commit -am用于提交跟踪过的文件 要理解它们的区别&#xff0c;首先要明白git的文件状态变化周期&#xff0c;如下图所示 工作目录下面的所有文件都不外乎这两种状态&#xff1a;已跟踪或未跟踪。已…

磁盘结构简介

这里讲的主要是网上所谓的老式磁盘&#xff0c;它是由一个个盘片组成的&#xff0c;我们先从个盘片结构讲起。如图1所示&#xff0c;图中的一圈圈灰色同心圆为一条条磁道&#xff0c;从圆心向外画直线&#xff0c;可以将磁道划分为若干个弧段&#xff0c;每个磁道上一个弧段被称…

end to end testing

概念 https://www.softwaretestinghelp.com/what-is-end-to-end-testing/ What is “End to End Testing”? Term “End to End testing” is defined as a testing method which determines whether the performance of an application is as per the requirement or not. It…

Linux目录架构详解

Linux和Windows操作系统的显著区别之一就是目录架构的不同。Linux操作系统的目录架构遵循文件系统层级结构标准。不知你是否使用ls命令浏览过Linux的根目录“/”&#xff0c;亲爱的读者&#xff0c;您都了解这些目录的含义吗&#xff1f; ls -l / 遍历文件系统&#xff08;点击…

Linux的学习:

查看端口&#xff1a; netstat -anop | grep 80 netstat -ntlp 先看看不带n的 再看看带n的 我们发现在local address 即主机地址这一栏中&#xff0c;如果没有带n选项&#xff0c;会将套接字所对应的域名解析出来&#xff0c;如果加上n选项&#xff0c;那么就不会显示&#xff…

Unix 多进程编程

一.多进程程序的特点由于UNIX系统是分时多用户系统, CPU按时间片分配给各个用户使用, 而在实质上应该说CPU按时间片分配给各个进程使用, 每个进程都有自己的运行环境以使得在CPU做进程切换时不会"忘记"该进程已计算了一半的"半成品". 以DOS的概念来说, 进程…

IT巨头互掐云存储:Dropbox能否一马当先

随着北京时间4月25日Google Drive横空出世&#xff0c;微软也迫不及待的发布了SkyDrive的大量更新。各大巨头进军云存储市场&#xff0c;激烈角逐的意向已经昭然可见。网友针对此事纷纷发表热议。苹果、微软、谷歌三巨头加上一个Dropbox各出各的云存储高招&#xff1a;微软SkyD…

Spring集成redis(Spring Data Redis)

2019独角兽企业重金招聘Python工程师标准>>> 转载地址&#xff1a;http://blog.csdn.net/zhu_tianwei/article/details/44923001 Spring-data-redis是spring大家族的一部分&#xff0c;提供了在srping应用中通过简单的配置访问redis服务&#xff0c;对reids底层开发…

PHP环境搭建和Apache HTTP服务器配置

所需软件: 需要准备Apache HTTP 服务器: http://httpd.apache.org/download.cgi PHP环境下载:http://www.php.net/downloads.php Apache HTTP服务器安装: 由于最新的 Apache 已经不提供 Windows 的安装版本了&#xff0c;所以我们这里使用的是解压版。 下载地址&#xff1a;htt…

ElasticSearch安装过程中遇到的一些问题

问题1&#xff1a; 安装Elasticsearch5.X版本&#xff0c;不修改默认配置的情况下&#xff0c;一切还好&#xff0c;能够正常启动。但我必须开通外网访问。然后报错了&#xff0c;报错信息如下&#xff1a; ERROR: max file descriptors [1024] for elasticsearch process like…

进程的五种状态和线程的六种状态

参考文章&#xff1a; 进程的状态转换和线程的状态转换

Nexus 安装(Linux 环境)

一、环境准备 安装 JDK 1.7 二、下载 Nexus 压缩文件 下载地址&#xff1a;http://www.sonatype.org/nexus/archived/ 三、上传压缩文件 四、解压缩文件 五、配置环境变量 1、临时配置 mvn clean package -Dmaven.test.skiptrue -Dmaven.javadoc.skiptrue命令&#xff1a;expor…

java.util.UnknownFormatConversionException: Conversion = ‘,‘ 解决

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 报错&#xff1a; 2. 原因&#xff1a; 出错代码&#xff1a; if(info.getRiceCount() < 0){ ... } 事实上 对象的riceCoun属性值…

Map转为String

Map转为String 最近在做redis缓存时&#xff0c;需要将一个Map<String,Object>的对象转换为String对象存入redis&#xff0c;用的时候需从redis中取出来后并转为Map<String,Objcet>对象。 之前的做法直接就是Map对象的toString()存入redis,后面取出是就是用再转换为…