Java有两种方式来让我们在运行时识别对象和类的信息。一种是RTTI,它假设在编译时已经知道了所有的类型;一种是反射机制,它允许我们在运行时发现和使用类的信息。

一、RTTI

使用RTTI会将所有子类都转化成相同的超类类型,然后使用它们的时候,会根据具体指向的类型调用各自的代码,也可以通过查询来判断对象的确切类型。

每个类都有一个Class对象,每次编译的时候都会生成,保存在.class文件中。它在类第一次被使用的时候,由JVM的类加载器将它加载到内存中。当程序创建第一个对类的静态成员引用时,就会记载这个类,所以构造器也是静态方法。一旦Class对象被加载到了内存中,它就被用来创建这个类的所有对象。Class.forName()获得Class对象的引用,通过这个引用可以获得很多有用的类信息。另外一种更简单安全的方法是通过className.class来获得Class对象的引用,因为它在编译时期就会受到检查。但是由于惰性的缘故,使用.class获得的对象引用并不是引发初始化,而Class.forName()会立即进行初始化。

综上,RTTI的形式有三种:

1、传统的类型转化,如(TypeName),由RTTI来确保正确性,如果出错,就会抛出ClassCastException异常。

2、代表对象类型的Class对象,通过查询它来获得运行时所需的信息。向上转型是自动的,而向下转型需要强制转化,因为编译器不知道这些。

3、使用熟知的instanceof,它返回判断的结果,使用Class.isInstance()方法可以进行动态的对象测试,使得代码更加简练。

二、反射

和RTTI不同,反射不需要类型在编译时已知,基于构件的编程和跨网络的远程平台创建和运行对象都需要用到反射,后者被称为RMI。由Class类和java.lang.reflect类库一起对反射的概念进行了支持。类库包含了Field、Method以及Constructor类,每个类都实现了Member接口。这些类型的对象由JVM在运行时创建,用以表示未知类里对应的成员。例如:通过Constructor创建新的对象,用get()和set()方法读取和修改Field对象关联的字段,用invoke()方法调用与Method对象关联的方法,调用getFields()、getMethods()和getConstructors()等方法来返回表示字段、方法以及构造器对象的数组。这样,匿名对象的类信息就能在运行时被完全确定下来,而在编译时不需要知道任何事情。反射的对象的.class文件对于JVM来说必须是可见的,要么在本地,要么可以通过网络获得。所以,RTTI和反射的根本区别在于,前者在编译时打开和检查.class文件,而后者在运行时打开和检查.class文件。