友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
第三电子书 返回本书目录 加入书签 我的书架 我的书签 TXT全本下载 『收藏到我的浏览器』

Java编程思想第4版[中文版](PDF格式)-第86部分

快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!


也有一个前提:句柄肯定也会被传递。但 Java 的设计方案似乎有些超前,允许我们忽略(大多数时候)自己 

处理的是一个句柄。也就是说,它允许我们将句柄假想成“对象”,因为在发出方法调用时,系统会自动照 

管两者间的差异。  

(2) Java主要按值传递(无自变量),但对象却是按引用传递的。得到这个结论的前提是句柄只是对象的一 

个“别名”,所以不考虑传递句柄的问题,而是直接指出“我准备传递对象”。由于将其传递进入一个方法 

时没有获得对象的一个本地副本,所以对象显然不是按值传递的。Sun 公司似乎在某种程度上支持这一见 

解,因为它“保留但未实现”的关键字之一便是byvalue (按值)。但没人知道那个关键字什么时候可以发 

挥作用。  

尽管存在两种不同的见解,但其间的分歧归根到底是由于对“句柄”的不同解释造成的。我打算在本书剩下 

的部分里回避这个问题。大家不久就会知道,这个问题争论下去其实是没有意义的——最重要的是理解一个 

句柄的传递会使调用者的对象发生意外的改变。  



                                                                351 


…………………………………………………………Page 353……………………………………………………………

12。2。2 克隆对象  



若需修改一个对象,同时不想改变调用者的对象,就要制作该对象的一个本地副本。这也是本地副本最常见 

的一种用途。若决定制作一个本地副本,只需简单地使用 clone()方法即可。Clone 是“克隆”的意思,即制 

作完全一模一样的副本。这个方法在基础类Object 中定义成“protected”(受保护)模式。但在希望克隆 

的任何衍生类中,必须将其覆盖为“public”模式。例如,标准库类Vector 覆盖了 clone(),所以能为 

Vector 调用clone(),如下所示:  

  

//: Cloning。java  

// The clone() operation works for only a few  

// items in the standard Java library。  

import java。util。*;  

  

class Int {  

  private int i;  

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

  public void increment() { i++; }  

  public String toString() {   

    return Integer。toString(i);   

  }  

}  

  

public class Cloning {  

  public static void main(String'' args) {  

    Vector v = new Vector();  

    for(int i = 0; i 《 10; i++ )  

      v。addElement(new Int(i));  

    System。out。println(〃v: 〃 + v);  

    Vector v2 = (Vector)v。clone();  

    // Increment all v2's elements:  

    for(Enumeration e = v2。elements();  

        e。hasMoreElements(); )  

      ((Int)e。nextElement())。increment();  

    // See if it changed v's elements:  

    System。out。println(〃v: 〃 + v);  

  }  

} ///:~  

  

clone()方法产生了一个Object,后者必须立即重新造型为正确类型。这个例子指出Vector 的 clone()方法 

不能自动尝试克隆Vector 内包含的每个对象——由于别名问题,老的Vector 和克隆的Vector 都包含了相同 

的对象。我们通常把这种情况叫作“简单复制”或者“浅层复制”,因为它只复制了一个对象的“表面”部 

分。实际对象除包含这个“表面”以外,还包括句柄指向的所有对象,以及那些对象又指向的其他所有对 

象,由此类推。这便是“对象网”或“对象关系网”的由来。若能复制下所有这张网,便叫作“全面复制” 

或者“深层复制”。  

在输出中可看到浅层复制的结果,注意对 v2 采取的行动也会影响到 v:  

  

v: '0; 1; 2; 3; 4; 5; 6; 7; 8; 9'  

v: '1; 2; 3; 4; 5; 6; 7; 8; 9; 10'  

  

一般来说,由于不敢保证Vector 里包含的对象是“可以克隆”(注释②)的,所以最好不要试图克隆那些对 

象。  

  

②:“可以克隆”用英语讲是 cloneable,请留意Java 库中专门保留了这样的一个关键字。  



                                                                                             352 


…………………………………………………………Page 354……………………………………………………………

12。2。3 使类具有克隆能力  



尽管克隆方法是在所有类最基本的 Object 中定义的,但克隆仍然不会在每个类里自动进行。这似乎有些不可 

思议,因为基础类方法在衍生类里是肯定能用的。但Java 确实有点儿反其道而行之;如果想在一个类里使用 

克隆方法,唯一的办法就是专门添加一些代码,以便保证克隆的正常进行。  

  

1。 使用protected 时的技巧  

为避免我们创建的每个类都默认具有克隆能力,clone()方法在基础类Object 里得到了“保留”(设为 

protected)。这样造成的后果就是:对那些简单地使用一下这个类的客户程序员来说,他们不会默认地拥有 

这个方法;其次,我们不能利用指向基础类的一个句柄来调用 clone() (尽管那样做在某些情况下特别有 

用,比如用多形性的方式克隆一系列对象)。在编译期的时候,这实际是通知我们对象不可克隆的一种方 

式——而且最奇怪的是,Java 库中的大多数类都不能克隆。因此,假如我们执行下述代码:  

Integer x = new Integer(l);  

x = x。clone();  

那么在编译期,就有一条讨厌的错误消息弹出,告诉我们不可访问clone()——因为Integer并没有覆盖 

它,而且它对protected 版本来说是默认的)。  

但是,假若我们是在一个从Object 衍生出来的类中(所有类都是从 Object 衍生的),就有权调用 

Object。clone(),因为它是“protected ”,而且我们在一个继承器中。基础类clone()提供了一个有用的功 

能——它进行的是对衍生类对象的真正“按位”复制,所以相当于标准的克隆行动。然而,我们随后需要将 

自己的克隆操作设为public,否则无法访问。总之,克隆时要注意的两个关键问题是:几乎肯定要调用 

super。clone(),以及注意将克隆设为 public。  

有时还想在更深层的衍生类中覆盖 clone(),否则就直接使用我们的clone() (现在已成为public),而那 

并不一定是我们所希望的(然而,由于Object。clone()已制作了实际对象的一个副本,所以也有可能允许这 

种情况)。protected 的技巧在这里只能用一次:首次从一个不具备克隆能力的类继承,而且想使一个类变 

成“能够克隆”。而在从我们的类继承的任何场合,clone()方法都是可以使用的,因为Java 不可能在衍生 

之后反而缩小方法的访问范围。换言之,一旦对象变得可以克隆,从它衍生的任何东西都是能够克隆的,除 

非使用特殊的机制(后面讨论)令其“关闭”克隆能力。  

  

2。 实现Cloneable 接口  

为使一个对象的克隆能力功成圆满,还需要做另一件事情:实现Cloneable 接口。这个接口使人稍觉奇怪, 

因为它是空的!  

interface Cloneable {}  

之所以要实现这个空接口,显然不是因为我们准备上溯造型成一个Cloneable,以及调用它的某个方法。有 

些人认为在这里使用接口属于一种“欺骗”行为,因为它使用的特性打的是别的主意,而非原来的意思。 

Cloneable interface 的实现扮演了一个标记的角色,封装到类的类型中。  

两方面的原因促成了Cloneable interface 的存在。首先,可能有一个上溯造型句柄指向一个基础类型,而 

且不知道它是否真的能克隆那个对象。在这种情况下,可用 instanceof 关键字(第 11章有介绍)调查句柄 

是否确实同一个能克隆的对象连接:  

if(myHandle instanceof Cloneable) // 。。。  

第二个原因是考虑到我们可能不愿所有对象类型都能克隆。所以Object。clone()会验证一个类是否真的是实 

现了Cloneable 接口。若答案是否定的,则“掷”出一个 CloneNotSupportedException 违例。所以在一般情 

况下,我们必须将“implement Cloneable”作为对克隆能力提供支持的一部分。  



12。2。4  成功的克隆  



理解了实现 clone()方法背后的所有细节后,便可创建出能方便复制的类,以便提供了一个本地副本:  

  

//: LocalCopy。java  

// Creating local copies with clone()  

import java。util。*;  

  

class MyObject implements Cloneable {  

  int i;  



                                                                           353 


…………………………………………………………Page 355……………………………………………………………

  MyObject(int ii) { i = ii; }  

  public Object clone() {  

    Object o = null;  

    try {  

      o = super。clone();  

    } catch (CloneNotSupportedException e) {  

      System。out。println(〃MyObject can't clone〃);  

    }  

    return o;  

  }  

  public String toString() {  

    return Integer。toString(i);  

  }  

}  

  

public class LocalCopy {  

  static MyObject g(MyObject v) {  

    // Passing a handle; modifies outside object:  

    v。i++;  

    return v;  

  }  

  static MyObject f(MyObject v) {  

    v = (MyObject)v。clone(); // Local copy  

    v。i++;  

    return v;  

  }  

  public static void main(String'' args) {  

    MyObject a = new MyObject(11);  

    MyObject b = g(a);  

    // Testing handle equivalence;  

    // not object equivalence:  

    if(a == b)   

      System。out。println(〃a == b〃);  

    else   

      System。out。println(〃a != b〃);  

    System。out。println(〃a = 〃 + a);  

    System。out。println(〃b = 〃 + b);  

    MyObject c = new MyObject(47);  

    MyObject d = f(c);  

    if(c == d)   

      System。out。println(〃c == d〃);  

    else   

      System。out。println(〃c != d〃);  

    System。out。println(〃c = 〃 + c);  

    System。out。println(〃d = 〃 + d);  

  }  

} ///:~  

  

不管怎样,clone()必须能够访问,所以必须将其设为 public (公共的)。其次,作为clone()的初期行动, 

应调用 clone()的基础类版本。这里调用的clone()是 Object 内部预先定义好的。之所以能调用它,是由于 

它具有 protected (受到保护的)属性,所以能在衍生的类里访问。  

Object。clone()会检查原先的对象有多大,再为新对象腾出足够多的内存,将所有二进制位从原来的对象复 



                                                                                             354 


…………………………………………………………Page 356……………………………………………………………

制到新对象。这叫作“按位复制”,而且按一般的想法,这个工作应该是由 clone()方法来做的。但在 

Object。clone()正式开始操作前,首先会检查一个类是否 Cloneable,即是否具有克隆能力——换言之,它 

是否实现了 Cloneable 接口。若未实现,Object。clone()就掷出一个 CloneNotSupportedException 违例,指 

出我们不能克隆它。因此,我们最好用一个try…catch 块将对 super。clone()的调用代码包围(或封装)起 

来,试图捕获一个应当永不出现的违例(因为这里确实已实现了Cloneable 接口)。  

在LocalCopy 中,两个方法g()和 f()揭示出两种参数传递方法间的差异。其中,g()演示的是按引用传递, 

它会修改外部对象,并返回对那个外部对象的一个引用。而f()是对自变量进行克隆,所以将其分离出来, 

并让原来的对象保持独立。随后,它继续做它希望的事情。甚至能返回指向这个新对象的一个句柄,而且不 

会对原来的对象产生任何副作用。注意下面这个多少有些古怪的语句:  

v = (MyObject)v。clone();  

它的作用正是创建一个本地副本。为避免被这样的一个语句搞混淆,记住这种相当奇怪的编码形式在Java 中 

是完全允许的,因为有一个名字的所有东西实际都是一个句柄。所以句柄 v 用于克隆一个它所指向的副本, 

而且最终返回指向基础类型Object 的一个句柄(因为它在 Object。clone()中是那样被定义的),随后必须 

将其造型为正确的类型。  

在main()中,两种不同参数传递方式的区别在于它们分别测试了一个不同的方法。输出结果如下:  

  

a == b  

a = 12  

b = 12  

c != d  

c = 47  

d = 48  

  

大家要记住这样一个事实:Java 对“是否等价”的测试并不对所比较对象的内部进行检查,从而核实它们的 

值是否相同。==和!=运算符只是简单地对比句柄的内容。若句柄内的地址相同,就认为句柄指向同样的对 

象,所以认为它们是“等价”的。所以运算符真正检测的是“由于别名问题,句柄是否指向同一个对象?”  



12。2。5 Object。clone()的效果  



调用Object。clone()时,实际发生的是什么事情呢?当我们在自己的类里覆盖 clone()时,什么东西对于 

super。clone()来说是最关键的呢?根类中的 clone()方法负责建立正确的存储容量,并通过“按位复制”将 

二进制位从原始对象中复制到新对象的存储空间。也就是说,它并不只是预留存储空间以及复制一个对象— 

—实际需要调查出欲复制之对象的准确大小,然后复制那个对象。由于所有这些工作都是在由根类定义之 

clone()方法的内部代码中进行的(根类并不知道要从自己这里继承出去什么),所以大家或许已经猜到,这 

个过程需要用RTTI 判断欲克隆的对象的实际大小。采取这种方式,clone()方法便可建立起正确数量的存储 

空间,并对那个类型进行正确的按位复制。  

不管我们要做什么,克隆过程的第一个部分通常都应该是调用 super。clone()。通过进行一次准确的复制, 

这样做可为后续的克隆进程建立起一个良好的基础。随后,可采取另一些必要的操作,以完成最终的克隆。  

为确切了解其他操作是什么,首先要正确理解 Object。clone()为
返回目录 上一页 下一页 回到顶部 1 1
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!