我不知道的事——深克隆和浅克隆_JAVA_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > JAVA > 我不知道的事——深克隆和浅克隆

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

 2012/4/10 13:52:57  风子柒  程序员俱乐部  我要评论(0)
  • 摘要:推荐一部好电影《致命魔术》。(此处为植入广告)推荐理由:涉及人性。画面不错,剧情跌宕,亦魔亦幻(此处的”魔“为魔术的”魔“)。虽然女猪脚不尽如人意,但是男猪脚比较帅。而且看完后有利于理解克隆,当然理解了克隆也利于观影!首先,简单客观地解释下几个关键的名词(我们约定A表示原来的对象,P表示A引用的对象;AC表示克隆后的A对象):浅克隆:复制克隆对象的基本信息及其对其他对象的引用。在改变AC对象的P对象时,那么也会改变A对象的P对象。深克隆:深克隆也会复制对象的基本信息以及其对其他对象的引用,但是
  • 标签:


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

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

       从前面浅克隆的定义上看,改变AC的P就能改变A的P,这样显得这种克隆更加像深克隆(都刨到别人祖坟了,够深的!)。但是,换个角度来看,这种克隆只是浅显的将一个对象拷贝出来了,并没有真正的去对这个对象进行深入地剖析,即没有剥离两者之间的依赖,使得A和AC更像一个对象的不同命名,因此,反而显得浅显了。深克隆的技术含量也较之浅克隆高点。
       为了方便理解,我将浅克隆形象化为一对连体双胞胎,而将深克隆形象化为一对同卵双胞胎;或者也可将浅克隆理解为镜像,而深克隆则是复制了一个真正具有独立行为能力的实体。
       下面详细对它们进行阐述:
       克隆
       实现克隆的类都必须实现Cloneable接口,而且一般需要重写Object类里的clone()方法。我们首先看看Object类中对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 {@code x}, the expression:
     * <blockquote>
     * <pre>
     * x.clone() != x</pre></blockquote>
     * will be true, and that the expression:
     * <blockquote>
     * <pre>
     * x.clone().getClass() == x.getClass()</pre></blockquote>
     * will be {@code true}, but these are not absolute requirements.
     * While it is typically the case that:
     * <blockquote>
     * <pre>
     * x.clone().equals(x)</pre></blockquote>
     * will be {@code true}, this is not an absolute requirement.
     * <p>
     * By convention, the returned object should be obtained by calling
     * {@code super.clone}.  If a class and all of its superclasses (except
     * {@code Object}) obey this convention, it will be the case that
     * {@code x.clone().getClass() == x.getClass()}.
     * <p>
     * By convention, the object returned by this method should be independent
     * of this object (which is being cloned).  To achieve this independence,
     * it may be necessary to modify one or more fields of the object returned
     * by {@code super.clone} before returning it.  Typically, this means
     * copying any mutable objects that comprise the internal "deep structure"
     * of the object being cloned and replacing the references to these
     * objects with references to the copies.  If a class contains only
     * primitive fields or references to immutable objects, then it is usually
     * the case that no fields in the object returned by {@code super.clone}
     * need to be modified.
     * <p>
     * The method {@code clone} for class {@code Object} performs a
     * specific cloning operation. First, if the class of this object does
     * not implement the interface {@code Cloneable}, then a
     * {@code CloneNotSupportedException} is thrown. Note that all arrays
     * are considered to implement the interface {@code Cloneable} and that
     * the return type of the {@code clone} method of an array type {@code T[]}
     * is {@code T[]} where T is any reference or primitive type.
     * Otherwise, this method creates a new instance of the class of this
     * object and initializes all its fields with exactly the contents of
     * the corresponding fields of this object, as if by assignment; the
     * contents of the fields are not themselves cloned. Thus, this method
     * performs a "shallow copy" of this object, not a "deep copy" operation.
     * <p>
     * The class {@code Object} does not itself implement the interface
     * {@code Cloneable}, so calling the {@code clone} method on an object
     * whose class is {@code Object} will result in throwing an
     * exception at run time.
     *
     * @return     a clone of this instance.
     * @exception  CloneNotSupportedException  if the object's class does not
     *               support the {@code Cloneable} interface. Subclasses
     *               that override the {@code clone} method can also
     *               throw this exception to indicate that an instance cannot
     *               be cloned.
     * @see java.lang.Cloneable
     */
	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。至于具体原因就涉及到克隆的作用了,等会的克隆的用途会详细说明。
       浅克隆的实现并不难,下面看一个示例:

class Sword{
		String name;
		float weight;
		public Sword(String name, float weight){
			this.name = name;
			this.weight = weight;
		} // end constructor
	} // end class Sword
	
	class Hero implements Cloneable{
		String name;
		int energy;	// hero的战斗值
		Sword s;
		public Hero(String name, int energy, Sword s){
			this.name = name;
			this.energy = energy;
			this.s = s;
		} // end constructor
		
		public void kill(){
			System.out.println("战斗值为" + energy + "的" + name + "挥动着重为"
					+ s.weight + "斤的" + s.name + "要开杀戒了!");
		} // end kill
		
		/**
		 * 重写Object的clone方法。
		 */
		public Object clone(){
			Hero h = null;
			try {
				h = (Hero)super.clone();
			} catch (CloneNotSupportedException e) {
				e.printStackTrace();
			} // end try-catch
			return h;
		} // end clone
	} // end class Hero
	
	public class ShallowClone{
		/**
		 * 主函数。
		 * @param args
		 */
		public static void main(String[] args) {
			// 声明一个Sword对象
			Sword s = new Sword("绝世好剑", 58.3f);
			// 声明一个Hero
			Hero h1 = new Hero("步惊云", 1000, s);
			h1.kill();
			// 克隆
			Hero h2 = (Hero) h1.clone();
			// 改变h2的s的一些属性
			h2.s.name = "草雉剑";
			h2.s.weight = 23.4f;
			h1.kill();
		if( !(h1 == h2)){
			System.out.println("从哲学的角度讲:此时的" + 
				h1.name + "已经不是从前的" + h1.name + "了!");
		}else{
			System.out.println("娃哈哈,我" + h1.name + "还是" + h1.name 					+ "!");
			} // end if-else
		} // end main	
	} // end class ShallowClone

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

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

class Sword implements Cloneable{
		String name;
		float weight;
		public Sword(String name, float weight){
			this.name = name;
			this.weight = weight;
		} // end constructor
		public Object clone(){
			try {
				return super.clone();
			} catch (CloneNotSupportedException e) {
				e.printStackTrace();
			} // end try-catch
			return null;
		} // end clone
	} // end class Sword

	class Hero implements Cloneable{
		String name;
		int energy;	// hero的战斗值
		Sword s;
		public Hero(String name, int energy, Sword s){
			this.name = name;
			this.energy = energy;
			this.s = s;
		} // end constructor
		public void kill(){
			System.out.println("战斗值为" + energy + "的" + name + "挥动着					重为" + s.weight + "斤的" + s.name + "要开杀戒了!");
		} // end kill
		/**
		 * 重写Object的clone方法。
		 */
		public Object clone(){
			Hero h = null;
			try {
				h = (Hero)super.clone();
				h.s = (Sword) s.clone();
			} catch (CloneNotSupportedException e) {
				e.printStackTrace();
			} // end try-catch
			return h;
		} // end clone
	} // end class Hero

	public class DeepClone{	
		/**
		 * 主函数。
	 	* @param args
	 	*/
		public static void main(String[] args) {
			// 声明一个Sword对象
			Sword s = new Sword("绝世好剑", 58.3f);
			// 声明一个Hero
			Hero h1 = new Hero("步惊云", 1000, s);
			h1.kill();
			// 克隆
			Hero h2 = (Hero) h1.clone();
			// 改变h2的s的一些属性
			h2.s.name = "草雉剑";
			h2.s.weight = 23.4f;
			h1.kill();
			if(! (h1 == h2)){
				System.out.println("从哲学的角度讲:此时的" + 
						h1.name + "已经不是从前的" + h1.name + "了!");
			}else{
				System.out.println("娃哈哈,我" + h1.name + "还是" + h1.name + "!");
			} // end if-else
		} // end main
	} // 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://weixiaolu.iteye.com/blog/1290821
  • 大小: 35.6 KB
  • 查看图片附件
  • 相关文章
发表评论
用户名: 匿名