按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
}
}
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++中,却可以使用
类似于句柄的东西,也能直接传递对象。这时便要用到C++的副本构建器:只要想获得一个对象,并按值传
递它,就可以复制对象。所以它在 C++里能很好地工作,但应注意这套机制在Java 里是很不通的,所以不要
用它。
12。4 只读类
尽管在一些特定的场合,由clone()产生的本地副本能够获得我们希望的结果,但程序员(方法的作者)不
得不亲自禁止别名处理的副作用。假如想制作一个库,令其具有常规用途,但却不能担保它肯定能在正确的
类中得以克隆,这时又该怎么办呢?更有可能的一种情况是,假如我们想让别名发挥积极的作用——禁止不
必要的对象复制——但却不希望看到由此造成的副作用,那么又该如何处理呢?
一个办法是创建“不变对象”,令其从属于只读类。可定义一个特殊的类,使其中没有任何方法能造成对象
内部状态的改变。在这样的一个类中,别名处理是没有问题的。因为我们只能读取内部状态,所以当多处代
码都读取相同的对象时,不会出现任何副作用。
作为“不变对象”一个简单例子,Java 的标准库包含了“封装器”(wrapper )类,可用于所有基本数据类
型。大家可能已发现了这一点,如果想在一个象Vector (只采用Object 句柄)这样的集合里保存一个 int
数值,可以将这个 int 封装到标准库的 Integer类内部。如下所示:
//: ImmutableInteger。java
// The Integer class cannot be changed
import java。util。*;
public class ImmutableInteger {
public static void main(String'' args) {
Vector v = new Vector();
for(int i = 0; i 《 10; i++)
v。addElement(new Integer(i));
// But how do you change the int
369
…………………………………………………………Page 371……………………………………………………………
// inside the Integer?
}
} ///:~
Integer类(以及基本的“封装器”类)用简单的形式实现了“不变性”:它们没有提供可以修改对象的方
法。
若确实需要一个容纳了基本数据类型的对象,并想对基本数据类型进行修改,就必须亲自创建它们。幸运的
是,操作非常简单:
//: MutableInteger。java
// A changeable wrapper class
import java。util。*;