`

Java的深度复制与浅层复制(一)

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

怎样应用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接口,并调用了Object的clone()方法(也就是调用了super.Clone()方法),那么Object的clone()方法就会抛出CloneNotSupportedException异常。

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

什么是影子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。

怎么进行深度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对象是一个例外,它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类的时候有了一个更多的选择。同时我们也可以把自己的类编成不可更改的类。

* 本文内容摘自 http://blog.csdn.net/guitacom/archive/2006/04/16/665055.aspx
分享到:
评论

相关推荐

    由浅层学习迈向深度学习——小学数学教学中深度学习点的设计与运用.pdf

    "由浅层学习迈向深度学习——小学数学教学中深度学习点的设计与运用" 本文主要探讨小学数学教学中深度学习点的设计与运用,旨在引导学生由浅层学习迈向深度学习。文章首先提出了问题情境的设置,强调情感驱动的价值...

    JavaScript浅层克隆与深度克隆示例详解

    JavaScript中的浅层克隆与深度克隆是两种不同的对象复制方式,它们主要处理对象和数组这类引用类型的数据。这两种克隆方法的区别在于处理对象内部引用值的方式,即对堆内存中对象的复制程度。 1. **浅层克隆**: ...

    从浅层学习到深度学习.pdf

    本文所指的深度学习,是指在传统浅层学习的基础上,能够对学生的学习过程进行深入、系统、持续的认知活动,从而更好地理解和运用知识,而浅层学习通常是一种表面的学习,主要依赖记忆、重复和简单的应用,缺乏对知识...

    《文本分类大综述:从浅层到深度学习》

    "文本分类大综述:从浅层到深度学习" 文本分类是自然语言处理中的基本任务之一。过去十年,深度学习的成功使得该领域的研究激增。为了满足全面和更新综述的需要,本综述从1961年到2020年对文本分类的发展进行了回顾...

    论浅层学习与深度学习.pptx

    浅层学习与深度学习是机器学习领域的两个重要分支,本文详细探讨了浅层学习和深度学习的概念、特点以及应用场景,并通过一个实践案例来具体分析它们的应用情况。 浅层学习 浅层学习是一种基于统计和概率模型的机器...

    浅层与深度强化学习“漏洞”分析报告PPT

    ### 浅层与深度强化学习“漏洞”分析报告 #### 知识点一:研究背景与应用背景 **研究背景**: 随着人工智能技术的发展,尤其是强化学习领域内的进步,研究者们越来越关注如何提高这些算法的安全性和鲁棒性。本研究...

    吴恩达深度学习课程第一课 第3周 浅层神经网络 编程作业(中、英文版本)

    在本节吴恩达老师的深度学习课程中,我们聚焦于第一课的主题——神经网络与深度学习,特别是第3周的内容,即浅层神经网络。这一周的编程作业旨在帮助学生深入理解并实践神经网络的基本构建块,无论是对于初学者还是...

    浅层LSTM改进HAR的深度学习

    浅层LSTM改进HAR的深度学习

    基于深度学习的地下浅层震源定位方法.pdf

    本文探讨了一种基于深度学习的地下浅层震源定位新方法,该方法采用了逆时聚焦技术和振幅叠加技术处理传感器阵列收集到的震动数据,并结合3D卷积神经网络(3D-CNN)建立端到端的学习模型来实现高精度的震源定位。...

    浅层地热能勘查评价规范

    《浅层地热能勘查评价规范》是中国一项关于浅层地热能资源勘查与评价的技术规范,其正式版为DZ/T 0225-2009。这项规范主要是为了确保在勘查和评价浅层地热能资源时能够统一标准、规范方法,提高勘查评价的准确性和...

    java_经典多线程编程

    浅层复制与深层复制 - **知识点描述**:复制对象时,有两种主要的方式:浅层复制和深层复制。浅层复制只复制对象的第一层引用,而深层复制会递归地复制所有引用的对象。 - **详细说明**: - **浅层复制**:仅仅...

    幼儿深度学习的基本特质与逻辑架构.pdf

    【幼儿深度学习与浅层学习的关系** 浅层学习通常关注知识的表面记忆,而深度学习则侧重于理解和应用。浅层学习为深度学习提供了基础知识,是深度学习的起点,而深度学习则在浅层学习的基础上,促进知识的整合、迁移...

    吴恩达Coursera深度学习课程 deeplearning.ai (1-3) 浅层神经网络--作业(可执行源码)

    课程的第一部分至第三部分主要关注浅层神经网络,这些部分的作业包含了可执行的源代码,让学生能够亲手实践所学知识。 在"浅层神经网络"的学习中,有几个关键知识点: 1. **神经网络基础**:首先,我们需要理解...

    从浅层到深层:基于深度学习的初中数学课堂优化路径.pdf

    本文认为,初中生的数学深度学习并不是对浅层学习的排斥,而是基于浅层学习的一种更高层次的认知活动。在深度学习过程中,学生不仅仅是被动接受知识,而是通过主动探索、问题解决、反思与调控,来加深对知识的理解,...

    神经网络与深度学习_神经网络与深度学习_深度学习_

    《神经网络与深度学习》是当今人工智能领域中最热门的话题之一,尤其对于初学者而言,它是一扇通往机器学习和人工智能世界的窗。这份资料涵盖了丰富的神经网络理论基础和深度学习实践应用,旨在帮助读者掌握这一领域...

    JavaScript 数组的深度复制解析

    对于javascript而言,数组是引用类型,如果要想复制一个数组就要动脑袋想想了,因为包括concat、slice在内的函数,都是浅层复制。也就是说,对于一个二维数组来说,用concat来做复制,第二维的数组还是引用,修改了...

    浅拷贝(浅复制、浅克隆)、深拷贝(深复制、深克隆)实战工程

    浅拷贝,也称为表面复制或浅克隆,是指创建一个新对象,这个新对象的属性与原对象的属性完全相同,但是指向同一块内存空间。如果原对象的属性是基本类型,那么新对象和原对象各自持有独立的副本;但如果属性是引用...

    对初中数学深度学习的理解与探究.pdf

    深度学习与浅层学习(Surface Learning)相对,它强调学生的学习过程应更多地体现出来,尊重学生的主动能动性,呼应学生的主动探究及主动合作。学生的主动性能打开深度学习的大门,让学习过程不再仅依赖于教师的评价...

Global site tag (gtag.js) - Google Analytics