Java编程思想第4版[中文版](PDF格式)-第97部分
按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
最初,Java 只是作为一种用于控制硬件的语言而设计,与因特网并没有丝毫联系。象这样一类面向大众的语
言一样,其意义在于程序员可以对任意一个对象进行克隆。这样一来,clone()就放置在根类Object 里面,
但因为它是一种公用方式,因而我们通常能够对任意一个对象进行克隆。看来这是最灵活的方式了,毕竟它
不会带来任何害处。
正当Java 看起来象一种终级因特网程序设计语言的时候,情况却发生了变化。突然地,人们提出了安全问
题,而且理所当然,这些问题与使用对象有关,我们不愿望任何人克隆自己的保密对象。所以我们最后看到
的是为原来那个简单、直观的方案添加的大量补丁:clone()在Object 里被设置成“protected”。必须将其
覆盖,并使用“implement Cloneable”,同时解决违例的问题。
只有在准备调用Object 的 clone()方法时,才没有必要使用 Cloneable 接口,因为那个方法会在运行期间得
到检查,以确保我们的类实现了Cloneable。但为了保持连贯性(而且由于Cloneable 无论如何都是空
的),最好还是由自己实现Cloneable。
362
…………………………………………………………Page 364……………………………………………………………
12。3 克隆的控制
为消除克隆能力,大家也许认为只需将clone()方法简单地设为private (私有)即可,但这样是行不通的,
因为不能采用一个基础类方法,并使其在衍生类中更“私有”。所以事情并没有这么简单。此外,我们有必
要控制一个对象是否能够克隆。对于我们设计的一个类,实际有许多种方案都是可以采取的:
(1) 保持中立,不为克隆做任何事情。也就是说,尽管不可对我们的类克隆,但从它继承的一个类却可根据
实际情况决定克隆。只有Object。clone()要对类中的字段进行某些合理的操作时,才可以作这方面的决定。
(2) 支持 clone(),采用实现 Cloneable (可克隆)能力的标准操作,并覆盖clone()。在被覆盖的clone()
中,可调用 super。clone(),并捕获所有违例(这样可使clone()不“掷”出任何违例)。
(3) 有条件地支持克隆。若类容纳了其他对象的句柄,而那些对象也许能够克隆(集合类便是这样的一个例
子),就可试着克隆拥有对方句柄的所有对象;如果它们“掷”出了违例,只需让这些违例通过即可。举个
例子来说,假设有一个特殊的 Vector,它试图克隆自己容纳的所有对象。编写这样的一个Vector 时,并不
知道客户程序员会把什么形式的对象置入这个 Vector 中,所以并不知道它们是否真的能够克隆。
(4) 不实现 Cloneable(),但是将clone()覆盖成 protected,使任何字段都具有正确的复制行为。这样一
来,从这个类继承的所有东西都能覆盖clone(),并调用 super。clone()来产生正确的复制行为。注意在我们
实现方案里,可以而且应该调用super。clone()——即使那个方法本来预期的是一个Cloneable 对象(否则
会掷出一个违例),因为没有人会在我们这种类型的对象上直接调用它。它只有通过一个衍生类调用;对那
个衍生类来说,如果要保证它正常工作,需实现Cloneable。
(5) 不实现 Cloneable 来试着防止克隆,并覆盖clone(),以产生一个违例。为使这一设想顺利实现,只有
令从它衍生出来的任何类都调用重新定义后的 clone()里的 suepr。clone()。
(6) 将类设为final,从而防止克隆。若clone()尚未被我们的任何一个上级类覆盖,这一设想便不会成功。
若已被覆盖,那么再一次覆盖它,并“掷”出一个 CloneNotSupportedException (克隆不支持)违例。为担
保克隆被禁止,将类设为final 是唯一的办法。除此以外,一旦涉及保密对象或者遇到想对创建的对象数量
进行控制的其他情况,应该将所有构建器都设为private,并提供一个或更多的特殊方法来创建对象。采用
这种方式,这些方法就可以限制创建的对象数量以及它们的创建条件——一种特殊情况是第 16 章要介绍的
singleton (独子)方案。
下面这个例子总结了克隆的各种实现方法,然后在层次结构中将其“关闭”:
//: CheckCloneable。java
// Checking to see if a handle can be cloned
// Can't clone this because it doesn't
// override clone():
class Ordinary {}
// Overrides clone; but doesn't implement
// Cloneable:
class WrongClone extends Ordinary {
public Object clone()
throws CloneNotSupportedException {
return super。clone(); // Throws exception
}
}
// Does all the right things for cloning:
class IsCloneable extends Ordinary
implements Cloneable {
public Object clone()
throws CloneNotSupportedException {
return super。clone();
}
}
363
…………………………………………………………Page 365……………………………………………………………
// Turn off cloning by throwing the exception:
class NoMore extends IsCloneable {
public Object clone()
throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
}
class TryMore extends NoMore {
public Object clone()
throws CloneNotSupportedException {
// Calls NoMore。clone(); throws exception:
return super。clone();
}
}
class BackOn extends NoMore {
private BackOn duplicate(BackOn b) {
// Somehow make a copy of b
// and return that copy。 This is a dummy
// copy; just to make the point:
return new BackOn();
}
public Object clone() {
// Doesn't call NoMore。clone():
return duplicate(this);
}
}
// Can't inherit from this; so can't override
// the clone method like in BackOn:
final class ReallyNoMore extends NoMore {}
public class CheckCloneable {
static Ordinary tryToClone(Ordinary ord) {
String id = ord。getClass()。getName();
Ordinary x = null;
if(ord instanceof Cloneable) {
try {
System。out。println(〃Attempting 〃 + id);
x = (Ordinary)((IsCloneable)ord)。clone();
System。out。println(〃Cloned 〃 + id);
} catch(CloneNotSupportedException e) {
System。out。println(
〃Could not clone 〃 + id);
}
}
return x;
}
public static void main(String'' args) {
// Upcasting:
364
…………………………………………………………Page 366……………………………………………………………
Ordinary'' ord = {
new IsCloneable();
new WrongClone();
new NoMore();
new TryMore();
new BackOn();
new ReallyNoMore();
};
Ordinary x = new Ordinary();
// This won't pile; since clone() is
// protected in Object:
//! x = (Ordinary)x。clone();
// tryToClone() checks first to see if
// a class implements Cloneable:
for(int i = 0; i 《 ord。length; i++)
tryToClone(ord'i');
}
} ///:~
第一个类Ordinary 代表着大家在本书各处最常见到的类:不支持克隆,但在它正式应用以后,却也不禁止对
其克隆。但假如有一个指向Ordinary 对象的句柄,而且那个对象可能是从一个更深的衍生类上溯造型来的,
便不能判断它到底能不能克隆。
WrongClone 类揭示了实现克隆的一种不正确途径。它确实覆盖了 Object。clone(),并将那个方法设为
public,但却没有实现Cloneable。所以一旦发出对super。clone()的调用(由于对Object。clone()的一个
调用造成的),便会无情地掷出CloneNotSupportedException 违例。
在 IsCloneable 中,大家看到的才是进行克隆的各种正确行动:先覆盖clone(),并实现了 Cloneable。但
是,这个clone()方法以及本例的另外几个方法并不捕获CloneNotSupportedException 违例,而是任由它通
过,并传递给调用者。随后,调用者必须用一个try…catch 代码块把它包围起来。在我们自己的clone()方
法中,通常需要在 clone()内部捕获CloneNotSupportedException 违例,而不是任由它通过。正如大家以后
会理解的那样,对这个例子来说,让它通过是最正确的做法。
类NoMore 试图按照Java 设计者打算的那样“关闭”克隆:在衍生类clone()中,我们掷出
CloneNotSupportedException 违例。TryMore 类中的clone()方法正确地调用 super。clone() ,并解析成
NoMore。clone(),后者掷出一个违例并禁止克隆。
但在已被覆盖的clone()方法中,假若程序员不遵守调用 super。clone()的“正确”方法,又会出现什么情况
呢?在BackOn 中,大家可看到实际会发生什么。这个类用一个独立的方法duplicate()制作当前对象的一个
副本,并在 clone()内部调用这个方法,而不是调用super。clone()。违例永远不会产生,而且新类是可以克
隆的。因此,我们不能依赖“掷”出一个违例的方法来防止产生一个可克隆的类。唯一安全的方法在
ReallyNoMore 中得到了演示,它设为 final,所以不可继承。这意味着假如clone()在 final 类中掷出了一
个违例,便不能通过继承来进行修改,并可有效地禁止克隆(不能从一个拥有任意继承级数的类中明确调用
Object。clone();只能调用 super。clone(),它只可访问直接基础类)。因此,只要制作一些涉及安全问题
的对象,就最好把那些类设为final。
在类CheckCloneable 中,我们看到的第一个类是tryToClone(),它能接纳任何Ordinary 对象,并用
instanceof检查它是否能够克隆。若答案是肯定的,就将对象造型成为一个 IsCloneable,调用clone(),
并将结果造型回Ordinary,最后捕获有可能产生的任何违例。请注意用运行期类型鉴定(见第 11章)打印
出类名,使自己看到发生的一切情况。
在main()中,我们创建了不同类型的Ordinary 对象,并在数组定义中上溯造型成为 Ordinary 。在这之后的
头两行代码创建了一个纯粹的 Ordinary 对象,并试图对其克隆。然而,这些代码不会得到编译,因为
clone()是 Object 中的一个protected (受到保护的)方法。代码剩余的部分将遍历数组,并试着克隆每个
对象,分别报告它们的成功或失败。输出如下:
Attempting IsCloneable
Cloned IsCloneable
365
…………………………………………………………Page 367……………………………………………………………
Attempting NoMore
Could not clone NoMore
Attempting TryMore
Could not clone TryMore
Attempting BackOn
Cloned BackOn
Attempting ReallyNoMore
Could not clone ReallyNoMore
总之,如果希望一个类能够克隆,那么:
(1) 实现Cloneable 接口
(2) 覆盖 clone()
(3) 在自己的clone()中调用super。clone()
(4) 在自己的clone()中捕获违例
这一系列步骤能达到最理想的效果。
12。3。1 副本构建器
克隆看起来要求进行非常复杂的设置,似乎还该有另一种替代方案。一个办法是制作特殊的构建器,令其负
责复制一个对象。在C++中,这叫作“副本构建器”。刚开始的时候,这好象是一种非常显然的解决方案
(如果你是 C++程序员,这个方法就更显亲切)。下面是一个实际的例子:
//: CopyConstructor。java
// A constructor for copying an object
// of the same type; as an attempt to create
// a local copy。
class FruitQualities {
private int weight;
private int color;
private int f