如果你不知道某个对象的确切类型,RTTI 可以告诉你。但是有一个限制:这个类型在编译
期必须已知,才能使用 RTTI 识别它,并利用这些信息做一些有用的事。换句话说,在编译
期,编译器必须知道你要通过 RTTI 来处理的所有类。
初看起来这似乎不是个限制,但是假设你获取了一个指向某个并不在你的程序空间中的对象
的引用。事实上,在编译期你的程序根本没法获知这个对象所属的类。例如,假设你从磁盘
文件,或者网络连接中获取了一串字节,并且你被告知这些字节代表了一个类。可是编译器
在编译你的程序代码的时候不可能了解有关这个在后面才会出现的类的信息。你怎样才能使
用这样的类呢?
在传统的编程环境中不太可能出现这种情况。但当我们置身于更大规模的编程世界中,在许
多 重 要 情 况 下 就 会 发 生 上 面 的 事 情 。 首 先 就 是 “ 基 于 构 件 的 编 程 ( component-based
programming)”,在此种编程方式中,你将使用某种基于“快速应用开发(RAD, Rapid
Application Development)”的应用构建工具来构建项目。这是一种可视化编程方法,你可以
通过将代表不同组件的图标拖曳到表单中来创建程序(在屏幕上看到的“表单(form)”就
是你所创建的程序)。然后在编程时通过设置构件的属性值来配置它们。这种设计期的配置,
要求构件都是可实例化的,并且要暴露其部分信息,以允许程序员读取和设置构件的值。此
外,处理 GUI 事件的构件还必须暴露相关方法的信息,以便 RAD 环境帮助程序员码重载这
些处理事件的方法。反射提供了一种机制,用来检查可用的方法,并返回方法名。Java 通过
JavaBean(第十四章将详细介绍)提供了基于构件的编程架构。
另一个让人们想要在运行期获取类的信息的动机,便是希望提供在跨网络的远程平台上创建
和运行对象的能力。这被称为“远程方法调用(RMI, Remote Method Invocation)”,
它允许一个 Java 程序将对象分布到多台机器上。需要这种分布能力是有许多原因的,例如,
你可能正在执行一项计算密集型的任务,为了提高运算速度,你想将计算划分为许多小的计
算单元,分布到空闲的机器上运行。另一种情况是,你可能希望将处理特定类型任务的代码
(例如多层的 C/S 客户服务器架构中的“业务规则 Business Rules”),置于特定的机
器上,于是这台机器就成为了描述这些动作的公共场所,你可以很容易地通过改动它就达到
影响系统中所有人的效果(这是一种有趣的开发方式,因为机器的存在仅仅是为了方便软件
的改动!)。同时,分布式计算也支持执行特殊任务的专有硬件,例如矩阵转置,而这对于通
用型程序就显得不太合适或者太昂贵了。
Class 类(本章前面已有论述)支持反射的概念,Java 附带的库 java.lang.reflect
包含了 Field,Method 以及 Constructor 类(每个类都实现了 Member 接口)。这些类
型的对象是由 JVM 在运行期创建的,用以表示未知类里对应的成员。这样你就可以使用
Constructor 创建新的对象,用 get()和 set()方法读取和修改与 Field 对象关联的属
性,用 invoke()方法调用与 Method 对象关联的方法。另外,你还可以调用 getFields(),
getMethods(),getConstructors()等等很便利的方法,以返回表示属性、方法以及
构造器的对象数组,这些对象(在 JDK 文档中,可找到与 Class 类相关的更多的资料)。
这样,匿名对象的类信息就能在运行期被完全确定下来,而在编译期不需要知道任何事情。
重要的是,反射机制并没有什么魔法。当你通过反射与一个未知类型的对象打交道时,JVM
只是简单地检查这个对象,看它属于哪个特定的类(就象 RTTI 那样)。但在这之后,在做
其它事情之前,必须加载那个类的 Class 对象。因此,那个类的.class 文件对于 JVM 来
说必须是可获取的,要么在本地机器上,要么可以通过网络取得。所以 RTTI 和反射之间真正的区别只在于,对 RTTI 来说,编译器在编译期打开和检查.class 文件。(换句话说,
我们可以用“普通”方式调用一个对象的所有方法。)而对于反射机制来说.class 文件在编
期必须已知,才能使用 RTTI 识别它,并利用这些信息做一些有用的事。换句话说,在编译
期,编译器必须知道你要通过 RTTI 来处理的所有类。
初看起来这似乎不是个限制,但是假设你获取了一个指向某个并不在你的程序空间中的对象
的引用。事实上,在编译期你的程序根本没法获知这个对象所属的类。例如,假设你从磁盘
文件,或者网络连接中获取了一串字节,并且你被告知这些字节代表了一个类。可是编译器
在编译你的程序代码的时候不可能了解有关这个在后面才会出现的类的信息。你怎样才能使
用这样的类呢?
在传统的编程环境中不太可能出现这种情况。但当我们置身于更大规模的编程世界中,在许
多 重 要 情 况 下 就 会 发 生 上 面 的 事 情 。 首 先 就 是 “ 基 于 构 件 的 编 程 ( component-based
programming)”,在此种编程方式中,你将使用某种基于“快速应用开发(RAD, Rapid
Application Development)”的应用构建工具来构建项目。这是一种可视化编程方法,你可以
通过将代表不同组件的图标拖曳到表单中来创建程序(在屏幕上看到的“表单(form)”就
是你所创建的程序)。然后在编程时通过设置构件的属性值来配置它们。这种设计期的配置,
要求构件都是可实例化的,并且要暴露其部分信息,以允许程序员读取和设置构件的值。此
外,处理 GUI 事件的构件还必须暴露相关方法的信息,以便 RAD 环境帮助程序员码重载这
些处理事件的方法。反射提供了一种机制,用来检查可用的方法,并返回方法名。Java 通过
JavaBean(第十四章将详细介绍)提供了基于构件的编程架构。
另一个让人们想要在运行期获取类的信息的动机,便是希望提供在跨网络的远程平台上创建
和运行对象的能力。这被称为“远程方法调用(RMI, Remote Method Invocation)”,
它允许一个 Java 程序将对象分布到多台机器上。需要这种分布能力是有许多原因的,例如,
你可能正在执行一项计算密集型的任务,为了提高运算速度,你想将计算划分为许多小的计
算单元,分布到空闲的机器上运行。另一种情况是,你可能希望将处理特定类型任务的代码
(例如多层的 C/S 客户服务器架构中的“业务规则 Business Rules”),置于特定的机
器上,于是这台机器就成为了描述这些动作的公共场所,你可以很容易地通过改动它就达到
影响系统中所有人的效果(这是一种有趣的开发方式,因为机器的存在仅仅是为了方便软件
的改动!)。同时,分布式计算也支持执行特殊任务的专有硬件,例如矩阵转置,而这对于通
用型程序就显得不太合适或者太昂贵了。
Class 类(本章前面已有论述)支持反射的概念,Java 附带的库 java.lang.reflect
包含了 Field,Method 以及 Constructor 类(每个类都实现了 Member 接口)。这些类
型的对象是由 JVM 在运行期创建的,用以表示未知类里对应的成员。这样你就可以使用
Constructor 创建新的对象,用 get()和 set()方法读取和修改与 Field 对象关联的属
性,用 invoke()方法调用与 Method 对象关联的方法。另外,你还可以调用 getFields(),
getMethods(),getConstructors()等等很便利的方法,以返回表示属性、方法以及
构造器的对象数组,这些对象(在 JDK 文档中,可找到与 Class 类相关的更多的资料)。
这样,匿名对象的类信息就能在运行期被完全确定下来,而在编译期不需要知道任何事情。
重要的是,反射机制并没有什么魔法。当你通过反射与一个未知类型的对象打交道时,JVM
只是简单地检查这个对象,看它属于哪个特定的类(就象 RTTI 那样)。但在这之后,在做
其它事情之前,必须加载那个类的 Class 对象。因此,那个类的.class 文件对于 JVM 来
说必须是可获取的,要么在本地机器上,要么可以通过网络取得。所以 RTTI 和反射之间真正的区别只在于,对 RTTI 来说,编译器在编译期打开和检查.class 文件。(换句话说,
我们可以用“普通”方式调用一个对象的所有方法。)而对于反射机制来说.class 文件在编
译期间是不可获取的,所以是在运行期打开和检查.class 文件。