`
endual
  • 浏览: 3558046 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

java中的clone详解

    博客分类:
  • java
 
阅读更多

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 接口,并调用了 Object clone() 法(也就是调用了 super.Clone() 方法),那么 Object clone() 方法就会抛出 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类的时候有了一个更多的 选择。同时我们也可以把自己的类编成不可更改的类。

分享到:
评论

相关推荐

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

    Java中的clone方法详解_动力节点Java学院,动力节点口口相传的Java黄埔军校

    java_clone用法

    ### Java中的`clone`方法详解:浅拷贝与深拷贝 #### 一、引言 在Java中,`clone`方法提供了一种快速复制对象的方式。它属于`Object`类的一部分,但需要显式地在子类中声明并实现`Cloneable`接口才能正常使用。本文...

    Clone详解.doc

    Java中的克隆(Clone)机制是一种创建对象副本的方法,它允许程序员复制一个对象的状态,而不会影响原始对象。克隆在编程中常用于创建对象的独立副本,使得新副本与原对象之间相互独立,对其中一个对象的修改不会...

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

    Java中的clone方法详解 在Java语言中,clone方法是一个非常重要的概念,它允许对象被复制,从而创造出一个新的对象。下面我们将详细介绍Java中的clone方法,并讨论它的实现机制和应用场景。 什么是clone方法 ...

    Java clone方法详解及简单实例

    Java中的`clone`方法是Java语言提供的一种复制对象的机制,它允许创建一个现有对象的副本,这个副本具有与原始对象相同的状态,但它们是独立的实体,对其中一个对象的修改不会影响另一个。`clone`方法是Java `Object...

    java 中clone()的使用方法

    Java 中 clone() 的使用方法 Java 中的 clone() 方法是对象的复制方法,其主要作用是创建一个与原对象相同的新对象。下面将详细介绍 Java 中 clone() 方法的使用方法。 什么是 clone() 方法? clone() 方法是 ...

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

    Java中的数组复制(clone与arraycopy)代码详解 Java中的数组复制是通过clone和arraycopy两个方法来实现的。clone方法是Object类的protected方法,用于创建对象的副本,而arraycopy是System类的静态方法,用于将一...

    对象克隆(clone)详解.docx

    在Java中,克隆主要涉及到`Object`类中的`clone()`方法,以及两种不同的克隆类型:浅克隆和深克隆。 一、克隆的原理与应用 `clone()`方法的工作原理是在堆上创建一个新的对象,这个新对象的内存分配与源对象相同,...

    java中关键字Object详解

    Java中的`Object`类是所有类的根,这意味着无论你定义的任何自定义类,如果没有显式地声明继承自其他类,那么它们都会隐式地继承`Object`类。`Object`类提供了基本的方法,这些方法是所有Java对象共有的。下面我们将...

    Java 数组复制clone方法实现详解

    首先,`clone()`方法是Java中的一个内置功能,它允许我们创建一个对象的浅拷贝。对于基本类型的数组,`clone()`会创建一个新的数组,然后复制原数组的所有元素到新数组中。然而,对于引用类型的数组(如对象数组),...

    详解Java中Object 类的使用.rar

    这个压缩包文件"详解Java中Object 类的使用.rar"包含了对Java中Object类的深入探讨,通过阅读其中的"详解Java中Object 类的使用.txt"文本,我们可以了解到关于Object类的一些关键知识点。 1. **对象的创建与类型...

    详解JAVA 原型模式

    【Java原型模式详解】 原型模式(Prototype Pattern)是一种创建型设计模式,它的主要目标是通过复制现有的对象来创建新对象,以减少重复的构造过程,提高性能。在Java中,原型模式通常涉及到对象的克隆操作,即...

    详解java中的深拷贝和浅拷贝(clone()方法的重写、使用序列化实现真正的深拷贝)

    在Java中,浅拷贝可以通过实现`Cloneable`接口并重写`clone()`方法来完成。以下是一个简单的浅拷贝示例: ```java public class Person implements Cloneable { private String name; private int age; private ...

    java中List的用法和实例详解

    在Java编程语言中,List接口是集合框架的重要组成部分,它属于Collection接口的子接口。List接口主要用于存储有序的、可重复的元素序列。与Set不同,List允许元素重复,并且保持插入时的顺序。本篇将详细介绍Java中...

    JAVA_高级特性(hashCode,clone,比较器,Class反射,序列化)

    ### Java 高级特性详解 #### 一、`hashCode` `hashCode` 方法是 `Object` 类中的一个方法,用于返回对象的哈希码值。在 Java 中,哈希码经常被用于实现散列表(如 `HashMap` 和 `HashSet`)。为了确保散列表的正确...

    Java函数习惯用法详解

    Java 函数习惯用法详解 Java 函数习惯用法是 Java 编程中非常重要的一部分,它直接影响着程序的效率和可读性。下面将对 Java 函数习惯用法进行详细的解释和总结。 一、equals() 函数 equals() 函数是 Java 中最...

    Java中对象的深复制和浅复制详解.doc

    Java中的`clone()`方法是`Object`类的一个成员方法,用于实现对象的复制。然而,使用`clone()`默认只会进行浅复制。要实现深复制,我们需要进行以下操作: - 在派生类中重写`clone()`方法,并声明为`public`,...

Global site tag (gtag.js) - Google Analytics