友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
Java编程思想第4版[中文版](PDF格式)-第61部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
排列方式造成的。若将一个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。 使用类标记
PetCount。java 示例可用Java 1。1 的类标记重写一遍。得到的结果显得更加明确易懂:
//: PetCount2。java
// Using Java 1。1 class literals
338
…………………………………………………………Page 340……………………………………………………………
package c11。petcount2;
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 PetCount2 {
public static void main(String'' args) {
Vector pets = new Vector();
Class'' petTypes = {
// Class literals work in Java 1。1+ only:
Pet。class;
Dog。class;
Pug。class;
Cat。class;
Rodent。class;
Gerbil。class;
Hamster。class;
};
try {
for(int i = 0; i 《 15; i++) {
// Offset by one to eliminate Pet。class:
int rnd = 1 + (int)(
Math。random() * (petTypes。length 1));
pets。addElement(
petTypes'rnd'。newInstance());
}
} catch(InstantiationException e) {}
catch(IllegalAccessException e) {}
Hashtable h = new Hashtable();
for(int i = 0; i 《 petTypes。length; i++)
h。put(petTypes'i'。toString();
new Counter());
for(int i = 0; i 《 pets。size(); i++) {
Object o = pets。elementAt(i);
if(o instanceof Pet)
((Counter)h。get(
〃class c11。petcount2。Pet〃))。i++;
if(o instanceof Dog)
((Counter)h。get(
〃class c11。petcount2。Dog〃))。i++;
if(o instanceof Pug)
((Counter)h。get(
〃class c11。petcount2。Pug〃))。i++;
if(o instanceof Cat)
339
…………………………………………………………Page 341……………………………………………………………
((Counter)h。get(
〃class c11。petcount2。Cat〃))。i++;
if(o instanceof Rodent)
((Counter)h。get(
〃class c11。petcount2。Rodent〃))。i++;
if(o instanceof Gerbil)
((Counter)h。get(
〃class c11。petcount2。Gerbil〃))。i++;
if(o instanceof Hamster)
((Counter)h。get(
〃class c11。petcount2。Hamster〃))。i++;
}
for(int i = 0; i 《 pets。size(); i++)
System。out。println(
pets。elementAt(i)。getClass()。toString());
Enumeration keys = h。keys();
while(keys。hasMoreElements()) {
String nm = (String)keys。nextElement();
Counter cnt = (Counter)h。get(nm);
System。out。println(
nm。substring(nm。lastIndexOf('。') + 1) +
〃 quantity: 〃 + cnt。i);
}
}
} ///:~
在这里,typenames (类型名)数组已被删除,改为从Class 对象里获取类型名称。注意为此而额外做的工
作:例如,类名不是Getbil,而是c11。petcount2。Getbil,其中已包含了包的名字。也要注意系统是能够区
分类和接口的。
也可以看到,petTypes 的创建模块不需要用一个try 块包围起来,因为它会在编译期得到检查,不会象
Class。forName()那样“掷”出任何违例。
Pet 动态创建好以后,可以看到随机数字已得到了限制,位于 1和 petTypes。length 之间,而且不包括零。
那是由于零代表的是Pet。class,而且一个普通的 Pet 对象可能不会有人感兴趣。然而,由于Pet。class 是
petTypes 的一部分,所以所有Pet (宠物)都会算入计数中。
2。 动态的 instanceof
Java 1。1 为Class 类添加了 isInstance方法。利用它可以动态调用 instanceof 运算符。而在Java 1。0
中,只能静态地调用它(就象前面指出的那样)。因此,所有那些烦人的 instanceof语句都可以从
PetCount 例子中删去了。如下所示:
//: PetCount3。java
// Using Java 1。1 isInstance()
package c11。petcount3;
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 {}
340
…………………………………………………………Page 342……………………………………………………………
class Counter { int i; }
public class PetCount3 {
public static void main(String'' args) {
Vector pets = new Vector();
Class'' petTypes = {
Pet。class;
Dog。class;
Pug。class;
Cat。class;
Rodent。class;
Gerbil。class;
Hamster。class;
};
try {
for(int i = 0; i 《 15; i++) {
// Offset by one to eliminate Pet。class:
int rnd = 1 + (int)(
Math。random() * (petTypes。length 1));
pets。addElement(
petTypes'rnd'。newInstance());
}
} catch(InstantiationException e) {}
catch(IllegalAccessException e) {}
Hashtable h = new Hashtable();
for(int i = 0; i 《 petTypes。length; i++)
h。put(petTypes'i'。toString();
new Counter());
for(int i = 0; i 《 pets。size(); i++) {
Object o = pets。elementAt(i);
// Using isInstance to eliminate individual
// instanceof expressions:
for (int j = 0; j 《 petTypes。length; ++j)
if (petTypes'j'。isInstance(o)) {
String key = petTypes'j'。toString();
((Counter)h。get(key))。i++;
}
}
for(int i = 0; i 《 pets。size(); i++)
System。out。println(
pets。elementAt(i)。getClass()。toString());
Enumeration keys = h。keys();
while(keys。hasMoreElements()) {
String nm = (String)keys。nextElement();
Counter cnt = (Counter)h。get(nm);
System。out。println(
nm。substring(nm。lastIndexOf('。') + 1) +
〃 quantity: 〃 + cnt。i);
}
}
341
…………………………………………………………Page 343……………………………………………………………
} ///:~
可以看到,Java 1。1 的isInstance()方法已取消了对 instanceof 表达式的需要。此外,这也意味着一旦要
求添加新类型宠物,只需简单地改变petTypes 数组即可;毋需改动程序剩余的部分(但在使用 instanceof
时却是必需的)。
11。2 RTTI 语法
Java 用Class 对象实现自己的RTTI 功能——即便我们要做的只是象造型那样的一些工作。Class 类也提供了
其他大量方式,以方便我们使用RTTI。
首先必须获得指向适当 Class 对象的的一个句柄。就象前例演示的那样,一个办法是用一个字串以及
Class。forName()方法。这是非常方便的,因为不需要那种类型的一个对象来获取 Class 句柄。然而,对于自
己感兴趣的类型,如果已有了它的一个对象,那么为了取得Class 句柄,可调用属于 Object 根类一部分的一
个方法:getClass()。它的作用是返回一个特定的Class 句柄,用来表示对象的实际类型。Class 提供了几
个有趣且较为有用的方法,从下例即可看出:
//: ToyTest。java
// Testing class Class
interface HasBatteries {}
interface Waterproof {}
interface ShootsThings {}
class Toy {
// ment out the following default
// constructor to see
// NoSuchMethodError from (*1*)
Toy() {}
Toy(int i) {}
}
class FancyToy extends Toy
implements HasBatteries;
Waterproof; ShootsThings {
FancyToy() { super(1); }
}
public class ToyTest {
public static void main(String'' args) {
Class c = null;
try {
c = Class。forName(〃FancyToy〃);
} catch(ClassNotFoundException e) {}
printInfo(c);
Class'' faces = c。getInterfaces();
for(int i = 0; i 《 faces。length; i++)
printInfo(faces'i');
Class cy = c。getSuperclass();
Object o = null;
try {
// Requires default constructor:
o = cy。newInstance(); // (*1*)
} catch(InstantiationException e) {}
catch(IllegalAccessException e) {}
342
…………………………………………………………Page 344……………………………………………………………
printInfo(o。getClass());
}
static void printInfo(Class cc) {
System。out。println(
〃Class name: 〃 + cc。getName() +
〃 is interface? '〃 +
cc。isInterface() + 〃'〃);
}
} ///:~
从中可以看出,class FancyToy 相当复杂,因为它从Toy 中继承,并实现了HasBatteries,Waterproof 以
及ShootsThings 的接口。在main()中创建了一个Class 句柄,并用位于相应 try 块内的 forName()初始化成
FancyToy 。
Class。getInterfaces 方法会返回 Class 对象的一个数组,用于表示包含在 Class 对象内的接口。
若有一个Class 对象,也可以用getSuperclass()查询该对象的直接基础类是什么。当然,这种做会返回一
个Class 句柄,可用它作进一步的查询。这意味着在运行期的时候,完全有机会调查到对象的完整层次结
构。
若从表面看,Class 的newInstance()方法似乎是克隆(clone())一个对象的另一种手段。但两者是有区别
的。利用newInstance() ,我们可在没有现成对象供“克隆”的情况下新建一个对象。就象上面的程序演示
的那样,当时没有Toy 对象,只有 cy——即y 的Class 对象的一个句柄。利用它可以实现“虚拟构建器”。
换言之,我们表达:“尽管我不知道你的准确类型是什么,但请你无论如何都正确地创建自己。”在上述例
子中,cy 只是一个Class 句柄,编译期间并不知道进一步的类型信息。一旦新建了一个实例后,可以得到
Object 句柄。但那个句柄指向一个Toy 对象。当然,如果要将除Object 能够接收的其他任何消息发出去,
首先必须进行一些调查研究,再进行造型。除此以外,用 newInstance()创建的类必须有一个默认构建器。
没有办法用 newInstance()创建拥有非默认构建器的对象,所以在Java 1。0 中可能存在一些限制。然而,
Java 1。1 的“反射”API (下一节讨论)却允许我们动态地使用类里的任何构建器。
程序中的最后一个方法是printInfo(),它取得一个Class 句柄,通过 getName()获得它的名字,并用
interface()调查它是不是一个接口。
该程序的输出如下:
Class name: FancyToy is interface? 'false'
Class name: HasBatteries is interface? 'true'
Class name: Waterproof is interface? 'true'
Class name: ShootsThings is interface? 'true'
Class name: Toy is interface? 'false'
所以利用Class 对象,我们几乎能将一个对象的祖宗十八代都调查出来。
11。3 反射:运行期类信息
如果不知道一个对象的准确类型,RTTI 会帮助我们调查。但却有一个限制:类型必须是在编译期间已知的,
否则就不能用RTTI 调查它,进而无法展开下一步的工作。换言之,编译器必须明确知道 RTTI 要处理的所有
类。
从表面看,这似乎并不是一个很大的限制,但假若得到的是一个不在自己程序空间内的对象的句柄,这时又
会怎样呢?事实上,对象的类即使在编译期间也不可由我们的程序使用。例如,假设我们从磁盘或者网络获
得一系列字节,而且被告知那些字节代表一个类。由于编译器在编译代码时并不知道那个类的情况,所以怎
样才能顺利地使用这个类呢?
在传统的程序设计环境中,出现这种情况的概率或许很小。但当我们转移到一个规模更大的编程世界中,却
必须对这个问题加以高度重视。第一个要注意的是基于组件的程序设计。在这种环境下,我们用“快�
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!