按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
个违例,便不能通过继承来进行修改,并可有效地禁止克隆(不能从一个拥有任意继承级数的类中明确调用
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 数组。
最后,注意每个类都有一个副本构建器,而且每个副本构建器都必须关心为基础类和成员对象调用副本构建
器的问题,从而获得“深层复制”的效果。对副本构建器的测试是在 CopyConstructor 类内进行的。方法
ripen()需要获取一个Tomato 参数,并对其执行副本构建工作,以便复制对象:
t = new Tomato(t);
而 slice()需要获取一个更常规的 Fruit 对象,而且对它进行复制:
f = new Fruit(f);
它们都在main()中伴随不同种类的Fruit 进行测试。下面是输出结果:
In ripen; t is a Tomato
In slice; f is a Fruit
In ripen; t is a Tomato
In slice; f is a Fruit
从中可以看出一个问题。在slice()内部对Tomato 进行了副本构建工作以后,结果便不再是一个 Tomato 对
象,而只是一个Fruit。它已丢失了作为一个Tomato (西红柿)的所有特征。此外,如果采用一个
GreenZebra,ripen()和 slice()会把它分别转换成一个 Tomato 和一个 Fruit。所以非常不幸,假如想制作对
象的一个本地副本,Java 中的副本构建器便不是特别适合我们。
1。 为什么在C++的作用比在 Java 中大?
副本构建器是C++的一个基本构成部分,因为它能自动产生对象的一个本地副本。但前面的例子确实证明了
它不适合在 Java 中使用,为什么呢?在 Java 中,我们操控的一切东西都是句柄,而在C++中,却可以�