`
hbkh2000
  • 浏览: 203880 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论
阅读更多

摘抄:浅复制和深度复制 clone()
1、什么是"clone"?什么时候用?
在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的。在Java语言中,用简单的赋值语句是不能满足这种需求的。要满足这种需求虽然有很多途径,但实现clone()方法是其中最简单,也是最高效的手段。
Java的所有类都默认继承java.lang.Object类,在java.lang.Object类中有一个方法clone()。JDK API的说明文档解释这个方法将返回Object对象的一个拷贝。要说明的有两点:一是拷贝对象返回的是一个新对象,而不是一个引用。二是拷贝对象与用new操作符返回的新对象的区别就是这个拷贝已经包含了一些原来对象的信息,而不是对象的初始信息。

 
2、怎样应用clone()方法?

 

一个很典型的调用clone()代码如下:

  1. class CloneClass implements Cloneable{    
  2.  public int aInt;    
  3.  public Object clone(){    
  4.   CloneClass o = null;    
  5.   try{    
  6.    o = (CloneClass)super.clone();    
  7.   }catch(CloneNotSupportedException e){    
  8.    e.printStackTrace();    
  9.   }    
  10.   return o;    
  11.  }    
  12. }   

     有三个值得注意的地方,一是希望能实现clone功能的CloneClass类实现了Cloneable接口,这个接口属于java.lang包,java.lang包已经被缺省的导入类中,所以不需要写成java.lang.Cloneable。另一个值得请注意的是重载了clone()方法。最后在clone()方法中调用了super.clone(),这也意味着无论clone类的继承结构是什么样的,super.clone()直接或间接调用了java.lang.Object类的clone()方法。下面再详细的解释一下这几点。

  应该说第三点是最重要的,仔细观察一下Object类的clone()一个native方法,native方法的效率一般来说都是远高于java中的非native方法。这也解释了为什么要用Object中clone()方法而不是先new一个类,然后把原始对象中的信息赋到新对象中,虽然这也实现了clone功能。对于第二点,也要观察Object类中的clone()还是一个protected属性的方法。这也意味着如果要应用clone()方法,必须继承Object类,在Java中所有的类是缺省继承Object类的,也就不用关心这点了。然后重载clone()方法。还有一点要考虑的是为了让其它类能调用这个clone类的clone()方法,重载之后要把clone()方法的属性设置为public。

  那么clone类为什么还要实现Cloneable接口呢?稍微注意一下,Cloneable接口是不包含任何方法的!其实这个接口仅仅是一个标志,而且这个标志也仅仅是针对Object类中clone()方法的,如果clone类没有实现Cloneable接口,并调用了Object的clone()方法(也就是调用了super.Clone()方法),那么Object的clone()方法就会抛出CloneNotSupportedException异常。

  以上是clone的最基本的步骤,想要完成一个成功的clone,还要了解什么是"影子clone"和"深度clone"。

 

"影子clone":对于对象只有基本数据类型是可以满足的;
"深度clone" :对于对象包含对象引用的话需要实现深度clone;

 

 影子clone实例代码如下:

 

/** 
 * 实现基本数据类型对象的Clone;
 */ 
class CloneBB implements Cloneable {
	public int aInt;
	public int bInt;

	// public UnCloneAA unCA = new UnCloneAA(111);
	public Object clone() {
		CloneBB o = null;
		try {
			o = (CloneBB) super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return o;
	}
}

public class CloneMain01 {
	public static void main(String[] a) {
		CloneBB b1 = new CloneBB();
		b1.aInt = 11;
		b1.bInt = 22;
		System.out.println("before clone,b1.aInt = " + b1.aInt);
		System.out.println("before clone,b1.aInt = " + b1.bInt);
		System.out.println("=========实现clone后========");
		CloneBB b2 = (CloneBB) b1.clone();
		System.out.println("after clone,b2.aInt = " + b2.aInt);
		System.out.println("after clone,b2.unCA = " + b2.bInt);
		System.out.println("============对b2进行改变======");
		b2.aInt = 33;
		b2.bInt = 44;
		System.out.println("after clone,b1.aInt = " + b1.aInt);
		System.out.println("after clone,b1.unCA = " + b1.bInt);
		System.out.println("after clone,b2.aInt = " + b2.aInt);
		System.out.println("after clone,b2.unCA = " + b2.bInt);
	}
}
//====Result===
//before clone,b1.aInt = 11
//before clone,b1.aInt = 22
//=========实现clone后========
//after clone,b2.aInt = 11
//after clone,b2.unCA = 22
//============对b2进行改变======
//after clone,b1.aInt = 11
//after clone,b1.unCA = 22
//after clone,b2.aInt = 33
//after clone,b2.unCA = 44

 

 将以上代码的类属性改变一下,见如下代码:

/**
 * 实现对象的Clone时当对象存在属性是对象时实现影子Clone是不可以满足的;
 */
class UnCloneA {
	private int i;

	public UnCloneA(int ii) {
		i = ii;
	}

	public void doublevalue() {
		i *= 2;
	}

	public String toString() {
		return Integer.toString(i);
	}
}

class CloneB implements Cloneable {
	public int aInt;

	public UnCloneA unCA = new UnCloneA(111);

	public Object clone() {
		CloneB o = null;
		try {
			o = (CloneB) super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return o;
	}
}

public class CloneMain02 {
	public static void main(String[] a) {
		CloneB b1 = new CloneB();
		b1.aInt = 11;
		System.out.println("before clone,b1.aInt = " + b1.aInt);
		System.out.println("before clone,b1.unCA = " + b1.unCA);
		CloneB b2 = (CloneB) b1.clone();
		b2.aInt = 22;
		b2.unCA.doublevalue();
		System.out.println("====对b2进行改变===");
		System.out.println("after clone,b1.aInt = " + b1.aInt);
		System.out.println("after clone,b1.unCA = " + b1.unCA);		
		System.out.println("after clone,b2.aInt = " + b2.aInt);
		System.out.println("after clone,b2.unCA = " + b2.unCA);
	}
}

// ====Result===
//before clone,b1.aInt = 11
//before clone,b1.unCA = 111
//====对b2进行改变===
//after clone,b1.aInt = 11
//after clone,b1.unCA = 222
//after clone,b2.aInt = 22
//after clone,b2.unCA = 222

 输出的结果说明int类型的变量aInt和UnCloneA的实例对象unCA的clone结果不一致,int类型是真正的被clone了,因为改变了b2中的aInt变量,对b1的aInt没有产生影响,也就是说,b2.aInt与b1.aInt已经占据了不同的内存空间,b2.aInt是b1.aInt的一个真正拷贝。相反,对b2.unCA的改变同时改变了b1.unCA,很明显,b2.unCA和b1.unCA是仅仅指向同一个对象的不同引用!从中可以看出,调用Object类中clone()方法产生的效果是:先在内存中开辟一块和原始对象一样的空间,然后原样拷贝原始对象中的内容。对基本数据类型,这样的操作是没有问题的,但对非基本类型变量,我们知道它们保存的仅仅是对象的引用,这也导致clone后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。
大多时候,这种clone的结果往往不是我们所希望的结果,这种clone也被称为"影子clone"。要想让b2.unCA指向与b2.unCA不同的对象,而且b2.unCA中还要包含b1.unCA中的信息作为初始信息,就要实现深度clone。

3、怎么进行深度clone?

    把上面的例子改成深度clone很简单,需要两个改变:一是让UnCloneA类也实现和CloneB类一样的clone功能(实现Cloneable接口,重载clone()方法)。二是在CloneB的clone()方法中加入一句o.unCA = (UnCloneA)unCA.clone();

 

/** 
 * 实现深度Clone; 
 */
class UnCloneAa implements Cloneable {
	private int i;

	public UnCloneAa(int ii) {
		i = ii;
	}

	public void doublevalue() {
		i *= 2;
	}

	public String toString() {
		return Integer.toString(i);
	}

	public Object clone() {
		UnCloneAa o = null;
		try {
			o = (UnCloneAa) super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return o;
	}
}

class CloneBobj implements Cloneable {
	public int aInt;
	public UnCloneAa unCA = new UnCloneAa(111);

	public Object clone() {
		CloneBobj o = null;
		try {
			o = (CloneBobj) super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		o.unCA = (UnCloneAa) unCA.clone();
		return o;
	}
}

public class CloneMain03 {
	public static void main(String[] a) {
		CloneBobj b1 = new CloneBobj();
		b1.aInt = 11;
		System.out.println("before clone,b1.aInt = " + b1.aInt);
		System.out.println("before clone,b1.unCA = " + b1.unCA);
		CloneBobj b2 = (CloneBobj) b1.clone();
		b2.aInt = 22;
		b2.unCA.doublevalue();
		System.out.println("====对b2进行改变后======");
		System.out.println("after clone,b1.aInt = " + b1.aInt);
		System.out.println("after clone,b1.unCA = " + b1.unCA);
		System.out.println("after clone,b2.aInt = " + b2.aInt);
		System.out.println("after clone,b2.unCA = " + b2.unCA);
	}
}

//---Result----
//before clone,b1.aInt = 11
//before clone,b1.unCA = 111
//====对b2进行改变后======
//after clone,b1.aInt = 11
//after clone,b1.unCA = 111
//after clone,b2.aInt = 22
//after clone,b2.unCA = 222

 可以看出,现在b2.unCA的改变对b1.unCA没有产生影响。此时b1.unCA与b2.unCA指向了两个不同的UnCloneA实例,而且在CloneB b2 = (CloneB)b1.clone();调用的那一刻b1和b2拥有相同的值,在这里,b1.i = b2.i = 11。


4、一些特殊情况的处理;

    要知道不是所有的类都能实现深度clone的。例如,如果把上面的CloneB类中的UnCloneA类型变量改成StringBuffer类型,看一下JDK API中关于StringBuffer的说明,StringBuffer没有重载clone()方法,更为严重的是StringBuffer还是一个final类,这也是说我们也不能用继承的办法间接实现StringBuffer的clone。如果一个类中包含有StringBuffer类型对象或和StringBuffer相似类的对象,我们有两种选择:要么只能实现影子clone,要么就在类的clone()方法中加一句(假设是SringBuffer对象,而且变量名仍是unCA): o.unCA = new StringBuffer(unCA.toString()); //原来的是:o.unCA = (UnCloneA)unCA.clone();

还要知道的是除了基本数据类型能自动实现深度clone以外,String对象是一个例外,它clone后的表现好象也实现了深度clone,虽然这只是一个假象,但却大大方便了我们的编程。

Clone中String和StringBuffer的区别

应该说明的是,这里不是着重说明String和StringBuffer的区别,但从这个例子里也能看出String类的一些与众不同的地方。

下面的例子中包括两个类,CloneC类包含一个String类型变量和一个StringBuffer类型变量,并且实现了clone()方法。在StrClone类中声明了CloneC类型变量c1,然后调用c1的clone()方法生成c1的拷贝c2,在对c2中的String和StringBuffer类型变量用相应的方法改动之后打印结果:

package clone;
class CloneC implements Cloneable{
public String str;
public StringBuffer strBuff;
public Object clone(){
CloneC o = null;
try{
o = (CloneC)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return o;
}

}
public class StrClone {
public static void main(String[] a){
CloneC c1 = new CloneC();
c1.str = new String("initializeStr");
c1.strBuff = new StringBuffer("initializeStrBuff");
System.out.println("before clone,c1.str = "+ c1.str);
System.out.println("before clone,c1.strBuff = "+ c1.strBuff);

CloneC c2 = (CloneC)c1.clone();
c2.str = c2.str.substring(0,5);
c2.strBuff = c2.strBuff.append(" change strBuff clone");
System.out.println("=================================");
System.out.println("after clone,c1.str = "+ c1.str);
System.out.println("after clone,c1.strBuff = "+ c1.strBuff);
System.out.println("=================================");
System.out.println("after clone,c2.str = "+ c2.str);
System.out.println("after clone,c2.strBuff = "+ c2.strBuff);
}
}
/* RUN RESULT
before clone,c1.str = initializeStr
before clone,c1.strBuff = initializeStrBuff
=================================
after clone,c1.str = initializeStr
after clone,c1.strBuff = initializeStrBuff change strBuff clone
=================================
after clone,c2.str = initi
after clone,c2.strBuff = initializeStrBuff change strBuff clone
*
*/

打印的结果可以看出,String类型的变量好象已经实现了深度clone,因为对c2.str的改动并没有影响到c1.str!难道Java把Sring类看成了基本数据类型?其实不然,这里有一个小小的把戏,秘密就在于c2.str = c2.str.substring(0,5)这一语句!实质上,在clone的时候c1.str与c2.str仍然是引用,而且都指向了同一个String对象。但在执行c2.str = c2.str.substring(0,5)的时候,它作用相当于生成了一个新的String类型,然后又赋回给c2.str。这是因为String被Sun公司的工程师写成了一个不可更改的类(immutable class),在所有String类中的函数都不能更改自身的值。下面给出很简单的一个例子:

package clone; public class StrTest { public static void main(String[] args) { String str1 = "This is a test for immutable"; String str2 = str1.substring(0,8); System.out.println("print str1 : " + str1); System.out.println("print str2 : " + str2); } } /* RUN RESULT print str1 : This is a test for immutable print str2 : This is */

例子中,虽然str1调用了substring()方法,但str1的值并没有改变。类似的,String类中的其它方法也是如此。当然如果我们把最上面的例子中的这两条语句

c2.str = c2.str.substring(0,5);
c2.strBuff = c2.strBuff.append(" change strBuff clone");

改成下面这样:

c2.str.substring(0,5);
c2.strBuff.append(" change strBuff clone");

去掉了重新赋值的过程,c2.str也就不能有变化了,我们的把戏也就露馅了。但在编程过程中只调用

c2.str.substring(0,5);

语句是没有任何意义的。

应该知道的是在Java中所有的基本数据类型都有一个相对应的类,象Integer类对应int类型,Double类对应double类型等等,这些类也与String类相同,都是不可以改变的类。也就是说,这些的类中的所有方法都是不能改变其自身的值的。这也让我们在编clone类的时候有了一个更多的选择。同时我们也可以把自己的类编成不可更改的类。

 

分享到:
评论

相关推荐

    C#中Clone一个对象的值到另一个对象案例 c#经典案例.pdf

    浅复制是 Clone 方法的默认实现,它只是复制对象的引用,而不复制对象的值。例如,下面的代码: ```csharp public class A : ICloneable { public int i; public string str; public object Clone() { return ...

    C#.net经典实例\全面剖析C_接口编程之实现接口_(1).doc

    // 实现Clone逻辑 return null; } // 显式实现IComparable.CompareTo() int IComparable.CompareTo(object other) { // 实现CompareTo逻辑 return 0; } } ``` 在上述代码中,`ListEntry` 类同时实现了 `...

    java Clone

    在实现`clone`方法时,通常我们会覆盖类中的`clone`方法,以便进行特定类型的克隆操作。例如: ```java public class CloneClass implements Cloneable { public int aInt; @Override protected Object clone()...

    学习Java实验7抽象类和接口.pdf

    为了实现对象内容深拷贝,我们还需要在 Name 类中实现 Cloneable 接口,并实现 clone 方法。 四、实验结论 通过本实验,我们学习了 Java 中的抽象类和接口的概念和使用方法,并且深入探讨了 Cloneable 接口和 ...

    如何通过JVM角度谈谈Java的clone操作

    Java中的对象创建主要有两种方式,即使用`new`操作符创建新对象以及通过`clone`方法...在实际编程中,还可以考虑使用其他技术,如拷贝构造函数或工厂方法,来替代或补充`clone`操作,以实现更安全、更可控的对象复制。

    java_clone用法

    本文将详细介绍`clone`的基本概念、工作原理以及如何实现浅拷贝和深拷贝。 #### 二、预备知识 在深入了解`clone`之前,我们需要了解几个基础概念: 1. **Java的类型**:Java中的数据类型主要分为两大类——原始类型...

    浅谈Java中实现深拷贝的两种方式—clone() & Serialized

    clone()方法需要我们手动实现clone()方法,并将所有涉及到的类实例clone一遍,而Serialized方法可以自动将对象转换为字节流,并生成一个新的对象。但是,Serialized方法需要对象实现Serializable接口,并且可能会...

    clone()示例源码

    在描述中提到的博客链接(由于无法直接访问,以下内容基于通常的`clone()`使用方式解释)可能会详细阐述如何在自定义类中重写`clone()`方法以实现更灵活的复制行为。通常,为了能够正确地克隆一个对象,你需要确保类...

    Java中的clone方法详解_动力节点Java学院整理

    在实现clone方法时,需要注意以下几点: * clone方法需要实现Cloneable接口。 * 在clone方法中,需要分配内存空间,然后再使用原对象中对应的各个域,填充新对象的域。 * 在clone方法中,需要处理对象中的引用类型...

    java clone的小例子

    如果一个类没有实现`Cloneable`接口,然后尝试调用`clone()`,系统会抛出`CloneNotSupportedException`异常。 下面是一个简单的示例,展示了如何使用`clone()`方法: ```java public class Student implements ...

    Java中的数组复制(clone与arraycopy)代码详解

    java提供一种叫浅拷贝(shallowcopy)的默认方式实现clone,创建好对象的副本后然后通过赋值拷贝内容。 2. arraycopy方法: arraycopy方法是System类的静态方法,用于将一个数组的内容复制到另一个数组中。该方法...

    Clone详解.doc

    - 实现`Cloneable`接口是调用`clone()`的前提,否则会抛异常。 - 重写`clone()`方法时,需要处理非基本类型的成员变量,确保它们也能够被正确地复制。 总的来说,Java中的克隆机制提供了复制对象的能力,这在很多...

    java clone

    2. 覆盖`clone`方法:在自定义类中,我们需要重写`clone`方法以实现特定的克隆逻辑。默认的`clone`方法是浅拷贝,如果需要深拷贝,需要在子类中自行处理。 下面是一些关于`clone`的实践心得: 1. 注意权限:默认...

    jquery.clone.js

    jquery.clone

    git代码clone,submodule

    可以通过结合 `git checkout` 和 `git submodule foreach` 来实现这一点。 **基本用法:** ```bash git checkout <branch-name> && git submodule foreach git checkout ``` **示例:** 假设我们需要将父项目切换到...

    git clone 最新版

    这可以通过访问Git的官方网站或者使用包管理器来实现。对于Windows用户,可以下载类似`Git-2.29.2.2-64-bit.exe`这样的安装程序,这是一个特定版本的Git二进制文件,包含最新功能和修复的安全问题。 Git的版本号由...

    Java深浅clone

    当尝试对未实现`Cloneable`接口的对象调用`clone()`方法时,会抛出`CloneNotSupportedException`异常。 浅克隆是指创建一个新对象,它的字段与原始对象的字段具有相同的引用。这意味着,如果对象的字段包含可变对象...

    clone-voice.zip

    "clone-voice.zip" 是一个压缩包文件,很可能包含了与语音克隆技术相关的代码或工具。根据提供的标签 "python",我们可以推测这个项目是使用Python编程语言实现的。Python在处理音频数据和人工智能领域有着广泛的...

    2019年最新版修订版Java程序员面试宝典.pdf

    实现clone需要实现Cloneable接口,并覆盖Object类的clone()方法。 4. Java没有goto语句:Java语言的设计者决定不包含goto语句,因为goto是一个非结构化的跳转语句,使用不当会导致程序的可读性和可维护性降低。 5....

Global site tag (gtag.js) - Google Analytics