按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
多传统语言都不同。
一旦那个类型的Class 对象进入内存,就用它创建那一类型的所有对象。
若这种说法多少让你产生了一点儿迷惑,或者并没有真正理解它,下面这个示范程序或许能提供进一步的帮
助:
//: SweetShop。java
// Examination of the way the class loader works
class Candy {
static {
System。out。println(〃Loading Candy〃);
}
}
class Gum {
static {
System。out。println(〃Loading Gum〃);
}
}
class Cookie {
static {
System。out。println(〃Loading Cookie〃);
}
}
public class SweetShop {
public static void main(String'' args) {
System。out。println(〃inside main〃);
new Candy();
System。out。println(〃After creating Candy〃);
try {
Class。forName(〃Gum〃);
} catch(ClassNotFoundException e) {
e。printStackTrace();
}
System。out。println(
〃After Class。forName(”Gum”)〃);
new Cookie();
System。out。println(〃After creating Cookie〃);
}
} ///:~
对每个类来说(Candy,Gum 和Cookie),它们都有一个 static从句,用于在类首次载入时执行。相应的信
息会打印出来,告诉我们载入是什么时候进行的。在main()中,对象的创建代码位于打印语句之间,以便侦
测载入时间。
特别有趣的一行是:
Class。forName(〃Gum〃);
该方法是Class (即全部Class 所从属的)的一个 static成员。而 Class 对象和其他任何对象都是类似的,
335
…………………………………………………………Page 337……………………………………………………………
所以能够获取和控制它的一个句柄(装载模块就是干这件事的)。为获得 Class 的一个句柄,一个办法是使
用forName()。它的作用是取得包含了目标类文本名字的一个String (注意拼写和大小写)。最后返回的是
一个Class 句柄。
该程序在某个JVM 中的输出如下:
inside main
Loading Candy
After creating Candy
Loading Gum
After Class。forName(〃Gum〃)
Loading Cookie
After creating Cookie
可以看到,每个Class 只有在它需要的时候才会载入,而 static 初始化工作是在类载入时执行的。
非常有趣的是,另一个 JVM 的输出变成了另一个样子:
Loading Candy
Loading Cookie
inside main
After creating Candy
Loading Gum
After Class。forName(〃Gum〃)
After creating Cookie
看来JVM 通过检查main()中的代码,已经预测到了对Candy 和Cookie 的需要,但却看不到Gum,因为它是通
过对forName()的一个调用创建的,而不是通过更典型的new 调用。尽管这个JVM 也达到了我们希望的效
果,因为确实会在我们需要之前载入那些类,但却不能肯定这儿展示的行为百分之百正确。
1。 类标记
在Java 1。1 中,可以采用第二种方式来产生Class 对象的句柄:使用“类标记”。对上述程序来说,看起来
就象下面这样:
Gum。class;
这样做不仅更加简单,而且更安全,因为它会在编译期间得到检查。由于它取消了对方法调用的需要,所以
执行的效率也会更高。
类标记不仅可以应用于普通类,也可以应用于接口、数组以及基本数据类型。除此以外,针对每种基本数据
类型的封装器类,它还存在一个名为TYPE 的标准字段。TYPE 字段的作用是为相关的基本数据类型产生 Class
对象的一个句柄,如下所示:
。。。 is equivalent to 。。。
boolean。class Boolean。TYPE
char。class Character。TYPE
byte。class Byte。TYPE
short。class Short。TYPE
int。class Integer。TYPE
long。class Long。TYPE
float。class Float。TYPE
double。class Double。TYPE
void。class Void。TYPE
336
…………………………………………………………Page 338……………………………………………………………
11。1。2 造型前的检查
迄今为止,我们已知的 RTTI 形式包括:
(1) 经典造型,如〃(Shape)〃,它用 RTTI 确保造型的正确性,并在遇到一个失败的造型后产生一个
ClassCastException 违例。
(2) 代表对象类型的Class 对象。可查询Class 对象,获取有用的运行期资料。
在C++中,经典的〃(Shape)〃造型并不执行RTTI 。它只是简单地告诉编译器将对象当作新类型处理。而Java
要执行类型检查,这通常叫作“类型安全”的下溯造型。之所以叫“下溯造型”,是由于类分层结构的历史
排列方式造成的。若将一个Circle (圆)造型到一个Shape (几何形状),就叫做上溯造型,因为圆只是几
何形状的一个子集。反之,若将Shape 造型至 Circle,就叫做下溯造型。然而,尽管我们明确知道Circle
也是一个Shape,所以编译器能够自动上溯造型,但却不能保证一个Shape 肯定是一个 Circle。因此,编译
器不允许自动下溯造型,除非明确指定一次这样的造型。
RTTI 在Java 中存在三种形式。关键字 instanceof告诉我们对象是不是一个特定类型的实例(Instance 即
“实例”)。它会返回一个布尔值,以便以问题的形式使用,就象下面这样:
if(x instanceof Dog)
((Dog)x)。bark();
将x 造型至一个 Dog 前,上面的 if语句会检查对象x 是否从属于 Dog 类。进行造型前,如果没有其他信息可
以告诉自己对象的类型,那么instanceof 的使用是非常重要的——否则会得到一个ClassCastException 违
例。
我们最一般的做法是查找一种类型(比如要变成紫色的三角形),但下面这个程序却演示了如何用
instanceof标记出所有对象。
//: PetCount。java
// Using instanceof
package c11。petcount;
import java。util。*;
class Pet {}
class Dog extends Pet {}
class Pug extends Dog {}
class Cat extends Pet {}
class Rodent extends Pet {}
class Gerbil extends Rodent {}
class Hamster extends Rodent {}
class Counter { int i; }
public class PetCount {
static String'' typenames = {
〃Pet〃; 〃Dog〃; 〃Pug〃; 〃Cat〃;
〃Rodent〃; 〃Gerbil〃; 〃Hamster〃;
};
public static void main(String'' args) {
Vector pets = new Vector();
try {
Class'' petTypes = {
Class。forName(〃c11。petcount。Dog〃);
Class。forName(〃c11。petcount。Pug〃);
Class。forName(〃c11。petcount。Cat〃);
Class。forName(〃c11。petcount。Rodent〃);
Class。forName(〃c11。petcount。Gerbil〃);
Class。forName(〃c11。petcount。Hamster〃);
337
…………………………………………………………Page 339……………………………………………………………
};
for(int i = 0; i 《 15; i++)
pets。addElement(
petTypes'
(int)(Math。random()*petTypes。length)'
。newInstance());
} catch(InstantiationException e) {}
catch(IllegalAccessException e) {}
catch(ClassNotFoundException e) {}
Hashtable h = new Hashtable();
for(int i = 0; i 《 typenames。length; i++)
h。put(typenames'i'; new Counter());
for(int i = 0; i 《 pets。size(); i++) {
Object o = pets。elementAt(i);
if(o instanceof Pet)
((Counter)h。get(〃Pet〃))。i++;
if(o instanceof Dog)
((Counter)h。get(〃Dog〃))。i++;
if(o instanceof Pug)
((Counter)h。get(〃Pug〃))。i++;
if(o instanceof Cat)
((Counter)h。get(〃Cat〃))。i++;
if(o instanceof Rodent)
((Counter)h。get(〃Rodent〃))。i++;
if(o instanceof Gerbil)
((Counter)h。get(〃Gerbil〃))。i++;
if(o instanceof Hamster)
((Counter)h。get(〃Hamster〃))。i++;
}
for(int i = 0; i 《 pets。size(); i++)
System。out。println(
pets。elementAt(i)。getClass()。toString());
for(int i = 0; i 《 typenames。length; i++)
System。out。println(
typenames'i' + 〃 quantity: 〃 +
((Counter)h。get(typenames'i'))。i);
}
} ///:~
在Java 1。0 中,对 instanceof 有一个比较小的限制:只可将其与一个已命名的类型比较,不能同Class 对
象作对比。在上述例子中,大家可能觉得将所有那些instanceof 表达式写出来是件很麻烦的事情。实际情况
正是这样。但在Java 1。0 中,没有办法让这一工作自动进行——不能创建Class 的一个Vector,再将其与
之比较。大家最终会意识到,如编写了数量众多的 instanceof表达式,整个设计都可能出现问题。
当然,这个例子只是一个构想——最好在每个类型里添加一个static数据成员,然后在构建器中令其增值,
以便跟踪计数。编写程序时,大家可能想象自己拥有类的源码控制权,能够自由改动它。但由于实际情况并
非总是这样,所以 RTTI 显得特别方便。
1。 使用类标记
Pet