为什么需要运行期类型鉴定 (RTTI)?由于除了在编译期就确定的引用外,如果在运行时也可以灵活地按需再次进行引用绑定,程序语言将更具灵活性和可扩展性,提高了复用率。更符合面向对象的设计要求。具体由于:面向对象思想 -> 多态 [同属性的类,执行同一的行为,可能需要不同的执行方式] -> 动态绑定 [父类引用指向子类对象] -> RTTI [运行期类型鉴定],而所谓的运行时类型信息(Runtime type information,RTTI),就是要求程序执行时具有保存其物件的类型信息的行为。某些语言实现仅保留有限的类型信息,例如继承树信息,而某些实现会保留较多信息,例如物件的属性及方法信息。那在Java中又是如何在执行一个类型的句柄时,去判断一个对象的正确类型并为其保存的。
面向对象思想 -> 多态 [同属性的类,执行同一的行为,可能需要不同的执行方式]
动物都具有吃的行为,而都属于动物的猫狗鸟猪,对于吃的方式又各自不同。我们希望仅调用一个更抽象的行为指令,使程序能够自动的分辨并执行更具体的行为。例如当动物去做出吃的动作时,但我们如何知道这个动作将要由何种动物去执行呢?可能是狗,当然也可能是猫。
当然我们甚至可以将动物抽象到生物,让生物代替狗来执行一个具体的行为,但这里会有一个麻烦,生物包括了植物,所以不能为生物统一定义一个吃的行为。或许我们可以为生物类增加各类生物不同的行为,例如代表植物的吸收的行为。但这样将会使生物类臃肿不堪,也脱离了面向对象的思想。我们可以考虑使用不同的抽象类去继承生物类,如具有吃行为的动物和具有吸收行为的植物。而生物类只需要生物普遍意义的生老病死就行。如果这时还要使用生物来代替狗来执行一个具体的行为,此时可能难以执行,因为生物类不存在更为具体的吃的行为,而吃的行为又只存在于动物类中。所以现在面临一个问题,我们需要生物具体为一个动物,再由动物去执行具体的动作。而这个时候又如何保证转化的顺利进行呢?
动态绑定 [父类引用指向子类对象] -> RTTI [运行期类型鉴定]
因此我们需要将生物类型向下转型为动物类型,然后通过动态绑定实现用动物类去执行具体狗的吃的行为。
动态绑定
执行期间(非编译期)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。程序运行过程中,把函数(或过程)调用与响应调用所需要的代码相结合的过程称为动态绑定。
RTTI [运行期类型鉴定]
在运行期,对象的类型会得到鉴定:
(1) 类型转换,它用 RTTI 确保造型的正确性,并在遇到一个失败的造型后产生一个ClassCastException 违例(2) 代表对象类型的 Class 对象。可查询 Class 对象,获取有用的运行期资料(3)关键字 instanceof 告诉我们对象是不是一个特定类型的实例( Instance 即“实例”)。它会返回一个布尔值
在运行期,程序是如何对对象的类型进行鉴定的? Java 里,通过一个名为“ Class 对象”的特殊形式的对象来表示在在运行期的类型信息,其中包含了与类有关的信息(有时也把它叫作“元类”)。
对于作为程序一部分的每个类,它们都有一个 Class 对象。换言之,每次写一个新类时,同时也会创建一个Class 对象(更恰当地说,是保存在一个完全同名的.class 文件中)。在运行期,一旦我们想生成那个类的一个对象,用于执行程序的 Java 虚拟机(JVM)首先就会检查那个类型的Class对象是否已经载入。若尚未载入,JVM就会查找同名的.class文件,并将其载入。所以Java程序启动时并不是完全载入的,这一点与许多传统语言都不同。一旦那个类型的Class对象进入内存,就用它创建那一类型的所有对象。
获得Class 的一个对象:
Class> ct1 = Class.forName("IO.test");Class> ct2 = test.class;Class> ct3 = 已实例化类对象. getClass(); //已有了对象,可调用属于 Object 根类的方法: getClass()。返回一个特定的 Class 句柄,用来表示对象的实际类型。
反射:运行期类信息
如果不知道一个对象的准确类型,RTTI会帮助我们调查。但却有一个限制:类型必须是在编译期间已知的,否则就不能用RTTI调查它,进而无法展开下一步的工作。换言之,编译器必须明确知道RTTI要处理的所有类。从表面看,这似乎并不是一个很大的限制,但假若得到的是一个不在自己程序空间内的对象的句柄,这时又会怎样呢?事实上,对象的类即使在编译期间也不可由我们的程序使用。例如,假设我们从磁盘或者网络获得一系列字节,而且被告知那些字节代表一个类。由于编译器在编译代码时并不知道那个类的情况,所以怎样才能顺利地使用这个类呢?
Class类需要扩展,使其可以支持“反射”的概念。针对Field,Method以及Constructor类(每个都实现了Memberinterface——成员接口),它们都新增了一个库:java.lang.reflect。这些类型的对象都是JVM在运行期创建的,用于代表未知类里对应的成员。这样便可用构建器创建新对象,用get()和set()方法读取和修改与Field对象关联的字段,以及用invoke()方法调用与Method对象关联的方法。此外,我们可调用方法 getFields(), getMethods(), getConstructors(),分别返回用于表示字段、方法以及构建器的对象数组(在联机文档中,还可找到与Class 类有关的更多的资料)。因此,匿名对象的类信息可在运行期被完整的揭露出来,而在编译期间不需要知道任何东西。大家要认识的很重要的一点是“反射”并没有什么神奇的地方。通过“反射”同一个未知类型的对象打交道时, JVM 只是简单地检查那个对象,并调查它从属于哪个特定的类(就象以前的 RTTI 那样)。但在这之后,在我们做其他任何事情之前, Class 对象必须载入。因此,用于那种特定类型的.class 文件必须能由 JVM 调用(要么在本地机器内,要么可以通过网络取得)。所以 RTTI和“反射”之间唯一的区别就是对RTTI来说,编译器会在编译期打开和检查.class文件。换句话说,我们可以用“普通”方式调用一个对象的所有方法;但对“反射”来说,.class文件在编译期间是不可使用的,而是由运行期环境打开和检查。