Java编程思想-第14章 类型信息(要点)
运行时类型信息使得你可以在程序运行时发现和使用类型信息。
它使你从只能在编译期执行面向类型的操作的禁锢中解脱了出来,并且可以使用某些非常 强大的程序。对RTTI的需要,揭示了面向对象设计中许多有趣(并且复杂)的问题,同时也提 出了如何组织程序的问题。 本章将讨论Java是如何让我们在运行时识别对象和类的信息的。主要有两种方式:一种是 “传统的”RTTI,它假定我们在编译时已经知道了所有的类型;另一种是“反射”机制,它允许 我们在运行时发现和使用类的信息。
14.1 为什么需要RTTI
在这个例子中,RTTI类型转换并不彻底: Object被转型为 Shape,而不是转型为 Cirele Square或者 Triangle。这是因为目前我们只知道这个List<Shape>保存的都是 Shape。在编译时 将由容器和Java的泛型系统来强制确保这一点;而在运行时,由类型转换操作来确保这一点。 接下来就是多态机制的事情了, Shape对象实际执行什么样的代码,是由引用所指向的具体 对象 Circle、 Square或者 Triangle而决定的。通常,也正是这样要求的;你希望大部分代码尽可 能少地了解对象的具体类型,而是只与对象家族中的一个通用表示打交道(在这个例子中是 Shape)。这样代码会更容易写,更容易读,且更便于维护;设计也更容易实现、理解和改变 所以“多态”是面向对象编程的基本目标 但是,假如你碰到了一个特殊的编程问题—一如果能够知道某个泛化引用的确切类型,就 可以使用最简单的方式去解决它,那么此时该怎么办呢?例如,假设我们允许用户将某一具体 类型的几何形状全都变成某种特殊的颜色,以突出显示它们。通过这种方法,用户就能找出屏 幕上所有被突出显示的三角形。或者,可能要用某个方法来旋转列出的所有图形,但想跳过圆 形,因为对圆形进行旋转没有意义。使用RTT,可以查询某个 Shape引用所指向的对象的确切 类型,然后选择或者剔除特例。
14.2 Class对象
要理解RTTI在Java中的工作原理,首先必须知道类型信息在运行时是如何表示的。这项工 作是由称为Clas对象的特殊对象完成的,它包含了与类有关的信息。事实上,Clas对象就是用 来创建类的所有的“常规”对象的。Java使用clas对象来执行其RTIl,即使你正在执行的是类 似转型这样的操作。Clas类还拥有大量的使用RTT的其他方式。
14.2.1 类字面常量
Java还提供了另一种方法来生成对Clas对象的引用,即使用类字面常量。对上述程序来说 就像下面这样 这样做不仅更简单,而且更安全,因为它在编译时就会受到检查(因此不需要置于try语句块中)。 并且它根除了对 forName()方法的调用,所以也更高效。
14.2.2 泛化的 Class引用
Class引用总是指向某个 Class对象,它可以制造类的实例,并包含可作用于这些实例的所有 方法代码。它还包含该类的静态成员,因此,Class引|用表示的就是它所指向的对象的确切类型, 而该对象便是Class类的一个对象。 但是, Java se5的设计者们看准机会,将它的类型变得更具体了一些,而这是通过允许你对 Cas9引用所指向的Cas对象的类型进行限定而实现的,这里用到了泛型语法。在下面的实例中 两种语法都是正确的
//: typeinfo/Genericclassreferences.java
public class GenericClassReferences{
public static void main(String[] args){
Class intClass = int.class;
Class<Integer> genericIntClass = int.class;
genericIntClass = Integer.Class; // Samething
intClass = double.class;
// genericIntClass = double class; // Illegal
}
}///:
14.2.3 新的转型语法
Java SE5 还添加了用于 Class引用的转型语法,即cast0方法:
14.3 类型转换前先做检查
14.3.1 使用类字面常量
14.3.2 动态的instanceof
Class.isInstence方法提供了一种动态地测试对象的途径。
14.3.3 递归计数
14.4 注册工厂
14.5 instanceof与Class的等价性
14.6 反射:运行时的类信息
如果不知道某个对象的确切类型,RTTI可以告诉你。但是有一个限制:这个类型在编译时 必须已知,这样才能使用RTT识别它,并利用这些信息做一些有用的事。换句话说,在编译时, 编译器必须知道所有要通过RTT来处理的类 初看起来这似乎不是个限制,但是假设你获取了一个指向某个并不在你的程序空间中的对 象的引|用;事实上,在编译时你的程序根本没法获知这个对象所属的类。例如,假设你从磁盘 文件,或者网络连接中获取了一串字节,并且你被告知这些字节代表了一个类。既然这个类在 编译器为你的程序生成代码之后很久才会出现,那么怎样才能使用这样的类呢 Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了 Field、 Method以及 Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行 时创建的,用以表示未知类里对应的成员。这样你就可以使用 Constructor创建新的对象,用 get0和setO方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的 方法。另外,还可以调用 getFields()、 getMethods()和 getConstructors()等很便利的方法,以返 回表示字段、方法以及构造器的对象的数组(在JDK文档中,通过查找Class类可了解更多相关 资料)。这样,匿名对象的类信息就能在运行时被完全确定下来,而在编译时不需要知道任何 事情。 重要的是,要认识到反射机制并没有什么神奇之处。当通过反射与一个未知类型的对象打 交道时,JⅥM只是简单地检查这个对象,看它属于哪个特定的类(就像RTTI那样)。在用它做其 他事情之前必须先加载那个类的 Class对象。因此,那个类的.class件对于JVM来说必须是可获 取的:要么在本地机器上,要么可以通过网络取得。所以RTTI和反射之间真正的区别只在于, 对RTTI来说,编译器在编译时打开和检查 .class文件。(换句话说,我们可以用“普通”方式调 用对象的所有方法。)而对于反射机制来说, .class件在编译时是不可获取的,所以是在运行时 打开和检查 .class文件。
14.6.1 类方法提取器
14.7 动态代理
代理是基本的设计模式之一,它是你为了提供额外的或不同的操作,而插入的用来代替 “实际”对象的对象。这些操作通常涉及与“实际”对象的通信,因此代理通常充当着中间人的 角色。
14.8 空对象
当你使用内置的null表示缺少对象时,在每次使用引用时都必须测试其是否为null,这显得 枯燥,而且势必产生相当乏味的代码。问题在于null除了在你试图用它执行任何操作来产生 NullPointerException之外,它自己没有其他任何行为。有时引引入空对象的思想将会很有用, 它可以接受传递给它的所代表的对象的消息,但是将返回表示为实际上并不存在任何“真实” 对象的值。通过这种方式,你可以假设所有的对象都是有效的,而不必浪费编程精力去检查null 并阅读所产生的代码)。
14.9 接口与类型信息
interface关键字的一种重要目标就是允许程序员隔离构件,进而降低耦合性。如果你编写 接口,那么就可以实现这一目标,但是通过类型信息,这种耦合性还是会传播出去——接口并 非是对解耦的一种无懈可击的保障。
14.10 总结
RTTI允许通过匿名基类的引用来发现类型信息。初学者极易误用它,因为在学会使用多态 调用方法之前,这么做也很有效。对有过程化编程背景的人来说,很难让他们不把程序组织成 系列 switch语句。你可以用RT做到这一点,但是这样就在代码开发和维护过程中损失了多 态机制的重要价值。面向对象编程语言的目的是让我们在凡是可以使用的地方都使用多态机制, 只在必需的时候使用RTTI 然而使用多态机制的方法调用,要求我们拥有基类定义的控制权,因为在你扩展程序的时 候,可能会发现基类并未包含我们想要的方法。如果基类来别人的类,或者由别人控制,这时 候RTTI便是一种解决之道:可继承一个新类,然后添加你需要的方法。在代码的其他地方,可 以检查你自己特定的类型,并调用你自己的方法。这样做不会破坏多态性以及程序的扩展能力 因为这样添加一个新的类并不需要在程序中搜索 switch语句。但如果在程序主体中添加需要的新 特性的代码,就必须使用RTTI来检查你的特定的类型。

浙公网安备 33010602011771号