友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
Java编程思想第4版[中文版](PDF格式)-第65部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 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 firmness;
private int ripeness;
private int smell;
// etc。
FruitQualities() { // Default constructor
// do something meaningful。。。
}
// Other constructors:
// 。。。
// Copy constructor:
FruitQualities(FruitQualities f) {
weight = f。weight;
color = f。color;
firmness = f。firmness;
ripeness = f。ripeness;
smell = f。smell;
// etc。
}
}
class Seed {
// Members。。。
Seed() { /* Default constructor */ }
366
…………………………………………………………Page 368……………………………………………………………
Seed(Seed s) { /* Copy constructor */ }
}
class Fruit {
private FruitQualities fq;
private int seeds;
private Seed'' s;
Fruit(FruitQualities q; int seedCount) {
fq = q;
seeds = seedCount;
s = new Seed'seeds';
for(int i = 0; i 《 seeds; i++)
s'i' = new Seed();
}
// Other constructors:
// 。。。
// Copy constructor:
Fruit(Fruit f) {
fq = new FruitQualities(f。fq);
seeds = f。seeds;
// Call all Seed copy…constructors:
for(int i = 0; i 《 seeds; i++)
s'i' = new Seed(f。s'i');
// Other copy…construction activities。。。
}
// To allow derived constructors (or other
// methods) to put in different qualities:
protected void addQualities(FruitQualities q) {
fq = q;
}
protected FruitQualities getQualities() {
return fq;
}
}
class Tomato extends Fruit {
Tomato() {
super(new FruitQualities(); 100);
}
Tomato(Tomato t) { // Copy…constructor
super(t); // Upcast for base copy…constructor
// Other copy…construction activities。。。
}
}
class ZebraQualities extends FruitQualities {
private int stripedness;
ZebraQualities() { // Default constructor
// do something meaningful。。。
}
ZebraQualities(ZebraQualities z) {
super(z);
367
…………………………………………………………Page 369……………………………………………………………
stripedness = z。stripedness;
}
}
class GreenZebra extends Tomato {
GreenZebra() {
addQualities(new ZebraQualities());
}
GreenZebra(GreenZebra g) {
super(g); // Calls Tomato(Tomato)
// Restore the right qualities:
addQualities(new ZebraQualities());
}
void evaluate() {
ZebraQualities zq =
(ZebraQualities)getQualities();
// Do something with the qualities
// 。。。
}
}
public class CopyConstructor {
public static void ripen(Tomato t) {
// Use the 〃copy constructor〃:
t = new Tomato(t);
System。out。println(〃In ripen; t is a 〃 +
t。getClass()。getName());
}
public static void slice(Fruit f) {
f = new Fruit(f); // Hmmm。。。 will this work?
System。out。println(〃In slice; f is a 〃 +
f。getClass()。getName());
}
public static void main(String'' args) {
Tomato tomato = new Tomato();
ripen(tomato); // OK
slice(tomato); // OOPS!
GreenZebra g = new GreenZebra();
ripen(g); // OOPS!
slice(g); // OOPS!
g。evaluate();
}
} ///:~
这个例子第一眼看上去显得有点奇怪。不同水果的质量肯定有所区别,但为什么只是把代表那些质量的数据
成员直接置入Fruit (水果)类?有两方面可能的原因。第一个是我们可能想简便地插入或修改质量。注意
Fruit 有一个protected (受到保护的)addQualities()方法,它允许衍生类来进行这些插入或修改操作(大
家或许会认为最合乎逻辑的做法是在Fruit 中使用一个protected 构建器,用它获取FruitQualities 参数,
但构建器不能继承,所以不可在第二级或级数更深的类中使用它)。通过将水果的质量置入一个独立的类,
可以得到更大的灵活性,其中包括可以在特定 Fruit 对象的存在期间中途更改质量。
之所以将FruitQualities 设为一个独立的对象,另一个原因是考虑到我们有时希望添加新的质量,或者通过
继承与多形性改变行为。注意对GreenZebra 来说(这实际是西红柿的一类——我已栽种成功,它们简直令人
368
…………………………………………………………Page 370……………………………………………………………
难以置信),构建器会调用addQualities(),并为其传递一个ZebraQualities 对象。该对象是从
FruitQualities 衍生出来的,所以能与基础类中的 FruitQualities 句柄联系在一起。当然,一旦
GreenZebra 使用 FruitQualities,就必须将其下溯造型成为正确的类型(就象evaluate()中展示的那
样),但它肯定知道类型是ZebraQualities。
大家也看到有一个 Seed (种子)类,Fruit (大家都知道,水果含有自己的种子)包含了一个Seed 数组。
最后,注意每个类都有一个副本构建器,而且每个副本构建器都必须关心为基础类和成员对象调用副本构建
器�
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!