为什么需要 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 引用所指向的对象的确
切类型,然后选择或者剔除特例。
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 引用所指向的对象的确
切类型,然后选择或者剔除特例。
相关文章推荐
- 从头认识java-12.1 为什么需要RTTI(Run-Time Type Identification)?
- 从头认识java-12.1 为什么需要RTTI(Run-Time Type Identification)?
- Android 为什么 dp2px 或 px2dp 公式需要加 0.5f
- 如果给一个单位做相关的软件,你认为最重要的是需要得到谁的支持,为什么
- 《Java程序员面试笔试宝典》之为什么需要public static void main(String[] args)这个方法
- 为什么需要auto_ptr_ref
- 为什么说基于TCP的移动端IM仍然需要心跳保活?
- BSS段为什么需要初始化
- 最优化问题中,牛顿法为什么比梯度下降法求解需要的迭代次数更少?
- 论测试人员为什么需要参加需求评审
- C#为什么多线程控制winform需要用委托?
- 在已有get,post情况下,为什么我们需要delete和put?
- 为什么执行测试用例时报需要 org.junit 4???
- Spark 中的RDD是什么东东?为什么需要它?
- 为什么需要输入验证码?
- 为什么TCP服务端需要调用bind函数而客户端通常不需要呢?
- 为什么每个专业人士都需要考虑写博客?
- 为什么通信服务提供商需要实现SD-WAN
- 为什么说任何基于比较的算法将 5 个元素排序都需要 7 次?
- 为什么需要DTO(数据传输对象)