您的位置:首页 > 其它

为什么需要 RTTI

2013-08-24 13:56 148 查看
让我们来思考已经很熟悉了的一个使用了多态的类层次结构的例子。最一般化的类型是基类

Shape,而派生出的具体类有 Circle,Square 和 Triangle。

这是一个典型的类层次结构图,基类位于顶部,派生类向下扩展。面向对象编程基本的目的

是:你的代码只操纵对基类(这里是 Shape)的引用。这样,如果你要添加一个新类(比

如从 Shape 派生 Rhomboid)来扩展程序,就不会影响到原来的代码。在这个例子的 Shape

接口中动态绑定了 draw()方法,目的就是让客户端程序员使用一般化的 Shape 的引用来

调用 draw()。draw()在所有派生类里都会被重载,并且由于它是被动态绑定的,所以即

使是通过通用的 Shape 引用来调用,也能产生正确行为。这就是多态(polymorphism)。

因此,我们通常会创建一个特定的对象(Circle,Square,或者 Triangle),把它向上

转型成 Shape(忽略对象的特定类型),并在后面的程序中使用匿名(译注:即不知道具体

类型)的 Shape 引用。

简要复习一下多态和向上类型转换,并为上面的例子编码:

//: c10:Shapes.java

import com.bruceeckel.simpletest.*;

class Shape {

void draw() { System.out.println(this + ".draw()"); }

}

class Circle extends Shape {

public String toString() { return
"Circle"; }

}

class Square extends Shape {

public String toString() { return
"Square"; }

}

class Triangle extends Shape {

public String toString() { return
"Triangle"; }

}

public class Shapes {

private static Test monitor =
new Test();

public static
void main(String[] args) {

    // Array of Object, not Shape:

    Object[] shapeList = {

new Circle(),

new Square(),

new Triangle()

    }; 

for(int i = 0; i < shapeList.length; i++)

      ((Shape)shapeList[i]).draw(); // Must cast

    monitor.expect(new String[] {

"Circle.draw()",

"Square.draw()",

"Triangle.draw()"

    });

  } 

} ///:~

基类中包含 draw()方法,它通过传递 this 参数给 System.out.println(),间接地使

用 toString()打印类标识符。如果是某个对象调用这个方法,它会自动调用 toString()

生成字符串。每个派生类都要重载(从 Object 类继承来的)toString()方法,这样 draw()

在不同情况下就打印出不同的消息。

在 main()中,生成了各种特定类型的
Shape,并加入到数组中。这个数组有点特别,因

为它不是一个 Shape 的数组(虽然它可以是),而是根类 Object 类的对象的数组。这样

做 的 原 因 是 为 第 十 一 章 作 准 备 , 我 们 将 学 习 collection
工 具 ( 也 被 称 作 容 器

container),它唯一的工作是保存与管理对象。基于通用性的考虑,这些 collection

应该能保存任何类型的对象,因此它们保存根类 Object 类的对象。Object 类的数组引出

了我们将在第十一章 Collection 中学习的一个重要的问题。

在这个例子中,当把 Shape 对象放入 Object 类的数组时会向上转型。由于在 Java 中所

有的对象都是根类 Object 类的对象(除了基本类型),所以一个 Object 的数组自然能保

存 Shape 类的对象。但在向上类型转换为 Object 的时候也失去了作为 Shape 对象特定

的信息。对于这个数组,它们就只是 Object 的对象。

当你通过索引操作符从数组中取出一个元素时,就要多做一些事情了。由于数组保存的只能

是 Object 对象,因此通过索引获得的也只是 Object 对象的引用。但我们知道那其实是

Shape 对象的引用,而且我们想给这个对象发送 Shape 对象能够接收的消息。所以必须使

用传统的“(Shape)”方式显式地将 Object 对象的引用转换成 Shape 对象的引用。这是

RTTI 最基本的使用形式,因为在 Java 中,所有的类型转换都是在运行期检查的。这也是

RTTI 名字的来源:在运行期间,识别一个对象的类型。

在这个例子中,RTTI 类型转换并不彻底:Object
被转型为 Shape,而不是转型为

Circle,Square,或者 Triangle。这是因为目前我们只知道这个数组保存的都是 Shape。

在编译时刻,这只能由你自己设定的规则来强制确保这一点,而在运行时刻,由类型转换操

作来确保这一点。

接下来就是多态机制的事情了,Shape 对象实际上执行什么样的代码,是由引用指向的具

体对象是 Circle,Square 或者 Triangle 而决定的。通常这正是它应该执行的行为;你

希望你的大部分代码尽可能少的了解对象特定的类型,而是只与一个对象家族中通用表示打

交道(在这个例子中是 Shape)。这样你的代码会更容易写,更容易读,并更便于维护,你

的设计也更容易实现、理解和改变。所以“多态”是面向对象编程的基本目标。

但是,假如你碰到了一个特殊的编程问题,如果你能够知道某个引用得确切类型,就可以使

用最简单的方式去解决它,那么此时你又该怎么办呢?例如,假设我们允许用户将某一具体

类型的几何形状全都变成紫色,以突出显示它们。通过这种方法,用户就能找出屏幕上所有

被突出显示的三角形。或者,你的方法可能被用来旋转列表中的所有图形,但你想跳过圆形,

因为对圆形作旋转没有意义。使用 RTTI,你可以查询某个 Shape 引用所指向的对象的确
切类型,然后选择或者剔除特例。



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: