友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
第三电子书 返回本书目录 加入书签 我的书架 我的书签 TXT全本下载 『收藏到我的浏览器』

Java编程思想第4版[中文版](PDF格式)-第82部分

快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!




11。1 对 RTTI 的需要  



请考虑下面这个熟悉的类结构例子,它利用了多形性。常规类型是Shape 类,而特别衍生出来的类型是 

Circle,Square 和Triangle 。  

  



                                         

  

这是一个典型的类结构示意图,基础类位于顶部,衍生类向下延展。面向对象编程的基本目标是用大量代码 

控制基础类型(这里是 Shape)的句柄,所以假如决定添加一个新类(比如Rhomboid ,从Shape 衍生),从 

而对程序进行扩展,那么不会影响到原来的代码。在这个例子中,Shape 接口中的动态绑定方法是draw(), 

所以客户程序员要做的是通过一个普通Shape 句柄调用draw()。draw()在所有衍生类里都会被覆盖。而且由 

于它是一个动态绑定方法,所以即使通过一个普通的Shape 句柄调用它,也有表现出正确的行为。这正是多 

形性的作用。  

所以,我们一般创建一个特定的对象(Circle,Square,或者 Triangle ),把它上溯造型到一个Shape (忽 

略对象的特殊类型),以后便在程序的剩余部分使用匿名 Shape 句柄。  

作为对多形性和上溯造型的一个简要回顾,可以象下面这样为上述例子编码(若执行这个程序时出现困难, 

请参考第3 章3。1。2 小节“赋值”):  

  

//: Shapes。java  

package c11;  

import java。util。*;  

  

interface Shape {  

  void draw();  

}  

  

class Circle implements Shape {  

  public void draw() {  

    System。out。println(〃Circle。draw()〃);  

  }  

}  

  

class Square implements Shape {  



                                                                                   333 


…………………………………………………………Page 335……………………………………………………………

  public void draw() {  

    System。out。println(〃Square。draw()〃);  

  }  

}  

  

class Triangle implements Shape {  

  public void draw() {  

    System。out。println(〃Triangle。draw()〃);  

  }  

}  

  

public class Shapes {  

  public static void main(String'' args) {  

    Vector s = new Vector();  

    s。addElement(new Circle());  

    s。addElement(new Square());  

    s。addElement(new Triangle());  

    Enumeration e = s。elements();  

    while(e。hasMoreElements())  

      ((Shape)e。nextElement())。draw();  

  }  

} ///:~  

  

基础类可编码成一个 interface (接口)、一个abstract (抽象)类或者一个普通类。由于Shape 没有真正 

的成员(亦即有定义的成员),而且并不在意我们创建了一个纯粹的Shape 对象,所以最适合和最灵活的表 

达方式便是用一个接口。而且由于不必设置所有那些abstract 关键字,所以整个代码也显得更为清爽。  

每个衍生类都覆盖了基础类draw 方法,所以具有不同的行为。在main()中创建了特定类型的Shape,然后将 

其添加到一个Vector。这里正是上溯造型发生的地方,因为Vector 只容纳了对象。由于Java 中的所有东西 

 (除基本数据类型外)都是对象,所以Vector 也能容纳 Shape 对象。但在上溯造型至 Object 的过程中,任 

何特殊的信息都会丢失,其中甚至包括对象是几何形状这一事实。对Vect or 来说,它们只是Object。  

用nextElement()将一个元素从 Vector 提取出来的时候,情况变得稍微有些复杂。由于 Vector 只容纳 

Object,所以 nextElement()会自然地产生一个 Object 句柄。但我们知道它实际是个 Shape 句柄,而且希望 

将Shape 消息发给那个对象。所以需要用传统的〃(Shape)〃方式造型成一个Shape。这是RTTI 最基本的形 

式,因为在 Java 中,所有造型都会在运行期间得到检查,以确保其正确性。那正是RTTI 的意义所在:在运 

行期,对象的类型会得到鉴定。  

在目前这种情况下,RTTI 造型只实现了一部分:Object 造型成 Shape,而不是造型成Circle,Square 或者 

Triangle 。那是由于我们目前能够肯定的唯一事实就是Vector 里充斥着几何形状,而不知它们的具体类别。 

在编译期间,我们肯定的依据是我们自己的规则;而在编译期间,却是通过造型来肯定这一点。  

现在的局面会由多形性控制,而且会为Shape 调用适当的方法,以便判断句柄到底是提供Circle,Square, 

还是提供给 Triangle 。而且在一般情况下,必须保证采用多形性方案。因为我们希望自己的代码尽可能少知 

道一些与对象的具体类型有关的情况,只将注意力放在某一类对象(这里是Shape)的常规信息上。只有这 

样,我们的代码才更易实现、理解以及修改。所以说多形性是面向对象程序设计的一个常规目标。  

然而,若碰到一个特殊的程序设计问题,只有在知道常规句柄的确切类型后,才能最容易地解决这个问题, 

这个时候又该怎么办呢?举个例子来说,我们有时候想让自己的用户将某一具体类型的几何形状(如三角 

形)全都变成紫色,以便突出显示它们,并快速找出这一类型的所有形状。此时便要用到RTTI 技术,用它查 

询某个 Shape 句柄引用的准确类型是什么。  



11。1。1 Class 对象  



为理解RTTI 在 Java 里如何工作,首先必须了解类型信息在运行期是如何表示的。这时要用到一个名为 

 “Class 对象”的特殊形式的对象,其中包含了与类有关的信息(有时也把它叫作“元类”)。事实上,我 

们要用 Class 对象创建属于某个类的全部“常规”或“普通”对象。  

对于作为程序一部分的每个类,它们都有一个 Class 对象。换言之,每次写一个新类时,同时也会创建一个 



                                                                                   334 


…………………………………………………………Page 336……………………………………………………………

Class 对象(更恰当地说,是保存在一个完全同名的。class 文件中)。在运行期,一旦我们想生成那个类的 

一个对象,用于执行程序的 Java 虚拟机(JVM)首先就会检查那个类型的Class 对象是否已经载入。若尚未 

载入,JVM 就会查找同名的。class 文件,并将其载入。所以Java 程序启动时并不是完全载入的,这一点与许 

多传统语言都不同。  

一旦那个类型的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(  
返回目录 上一页 下一页 回到顶部 1 1
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!