java中的clone详解

3.java 中的 clone

3.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操作符返回的新对象的区别就是这个拷贝已经包含了一些原来对象的信息,而不是对象的初始信息。

3.2. 怎样应用clone() 方法?

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

class CloneClass implements Cloneable{
public int aInt;
public Object clone(){
CloneClass o = null;
try{
o = (CloneClass)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return o;
}

有三个值得注意的地方,

一是希望能实现 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 接口,并调用了 Objectclone() 方 法(也就是调用了 super.Clone() 方法),那么 Objectclone() 方法就会抛出 CloneNotSupportedException 异常。

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

3.3 什么是影子clone

下面的例子包含三个类 UnCloneA, CloneB, CloneMain。 CloneB类包含了一个 UnCloneA的实例和一个 int类型变量,并且 重载 clone()方法。 CloneMain类初始化 UnCloneA类的一个实例 b1,然后调用 clone()方法生成了一个 b1的拷贝 b2。最后考察 一下 b1和 b2的输出:

package 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 CloneMain {
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("=================================");
System.out.println("after clone,b1.aInt = "+ b1.aInt);
System.out.println("after clone,b1.unCA = "+ b1.unCA);
System.out.println("=================================");
System.out.println("after clone,b2.aInt = "+ b2.aInt);
System.out.println("after clone,b2.unCA = "+ b2.unCA);
}
}


/** RUN RESULT:
before clone,b1.aInt = 11
before clone,b1.unCA = 111
=================================
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.4 怎么进行深度clone

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

程序如下:

package clone.ext;
class UnCloneA implements Cloneable{
private int i;
public UnCloneA(int ii) { i = ii; }
public void doublevalue() { i *= 2; }
public String toString() {
return Integer.toString(i);
}
public Object clone(){
UnCloneA o = null;
try{
o = (UnCloneA)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return o;
}
}
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();
}
o.unCA = (UnCloneA)unCA.clone();
return o;
}
}
public class CloneMain {
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("=================================");
System.out.println("after clone,b1.aInt = "+ b1.aInt);
System.out.println("after clone,b1.unCA = "+ b1.unCA);
System.out.println("=================================");
System.out.println("after clone,b2.aInt = "+ b2.aInt);
System.out.println("after clone,b2.unCA = "+ b2.unCA);
}
}

/** RUN RESULT:
before clone,b1.aInt = 11
before clone,b1.unCA = 111
=================================
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。

要知道不是所有的类都能实现深度 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对象, Integer, Double等是一个例外,它 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类的时候有了一个更多的 选择。同时我们也可以把自己的类编成不可更改的类。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值