类型信息

运行时类型信息使得可以在程序运行时发现和使用类型信息。

1.为什么需要RTTI(运行时类型信息)

类型信息
这是一个类层次的结构图,基类位于顶部,派生类向下扩展。面向对象的基本目的是让代码只操纵对基类的引用,这样如果要添加一个新的类来扩展程序,就不会影响到原来的代码。该例子中的shape接口动态绑定draw(),目的是让客户端程序员使用繁华的shape引用来调用draw()。draw在所有的派生类都会被覆盖,并且由于他是被动态绑定的,所以即使是通过繁华的shape引用来调用,也产生正确的行为。
类型信息
类型信息
基类中的draw方法,通过传递this参数给System.out.println(),间接使用toString()打印类标识符。当某个对象出现在字符串表达式中,toString方法会被自动调用,来生成表示该对象的String。每个派生类都要覆盖toString()方法,这样draw()在不同情况就会打印不同的消息。当把shape对象放入List<Shape>的数组时会向上转型。在向上转型的过程中也丢失了shape对象的具体类型,对于数组而言只是shape类的对象。当从数组中取出元素时,这种容器–经所有的事务都当做Object持有–会自动将结果转型回shape。在java中所有类型的转换都是在运行时进行正确性检查的。该例子中Object被转型为shape而不是Circle、square或triangle。

2.Class对象

class对象就是用来创建类的所偶的“常规”对象的。java使用class对象执行RTTI。类是程序的一部分,每个类都有一个class对象。为生成这个类的对象,运行这个程序的java虚拟机将使用被称为“类加载器”的子系统。类加载器子系统实际上可以包含一条类加载器链,但是只有一个原生类加载器。元类加载器加载得是可信类。
所有的类都是在对其第一次使用时动态加载到JVM中,当程序创建第一个对类的静态成员的引用时,就会加载这个类。因此,java程序在他开始裕兴之前并非被完全加载,其各个部分是在必须是才加载的。
类加载器首先检查这个类的Class对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找.class文件。在这个类的字节码被加载时,会接受验证,以确保其没有被破坏。一旦某个类的Class对象被加载到内存中,它就被用来创建这个类的所有对象。
类型信息
类型信息
从输出中看到,Class对象尽在需要的时候才被加载,static初始化是在类加载时进行的。Class.forName(“Gum”),该方法是Class类的static成员,Class对象和其他对象一样,可以获取并操作它的引用。forName()是取得Class对象引用的一种方法,用一个包含目标类的文本名的String作为输入参数,返回一个Class对象的引用。
无论何时,只要想在运行时使用类型信息,必须首先获得对恰当的Class对象的引用。Class.forName就可以实现。该方法不需要为了获得Class引用而持有该类型的对象。
Class的newInstance()方法实现“虚拟构造器”,虚拟构造器允许声明“我不知道你的确切类型,但是无论如何要正确的创建自己”使用newInstance()创建的类必须带有默认构造器。

2.1 类字面常量

java提供另一个方法生成对Class对象的引用,如FancyToy.class;这种在编译时会受到检查,根除对forName方法的调用,所以高效和安全。
类字面常量可以用于类、接口、数组、基本数据类型。对于基本数据类型的包装器类还有一个标准字段TYPE,TYPE字段是一个引用,指向对应基本数据类型的Class对象。
类型信息
为了使用类做的准备工作:
1.加载,2.链接,3.初始化。
初始化被延迟到对静态方法或者非常数静态域进行首次引用时才执行。
初始化有效的时间尽可能的“惰性”,从对initable引用的创建中可以看到,仅使用.class获得对类的引用不会引发初始化。但是为了产生Class引用,Class.forName()立即就进行了初始化。如果一个static域不是final的,那么在对他的访问时,总是要求在它被读取之前,进行链接。

2.2 泛化的Class引用

Class引用总是指向某个Class对象,可以制作类的实例,并包含可做用于这些事例的所有方法代码。还包含该类的静态成员,因此Class引用表示就是他所指向的对象的确切的类型。
为了在使用泛化的Class引用时放松限制,使用通配符。Class<?>的好处在于表示并非碰巧或者由于疏忽而使用了一个非具体的类引用。向Class引用添加泛型语法的原因是为了提供编译期类型检查。
下面的例子使用泛型类语法。
类型信息
当将泛型语法用于Class对象会发生newInstance将返回该对象的确切类型,不仅只是在ToyTest.java中的基本Object。

2.3 新的转型语法

类型信息
cast方法接收参数对象,并且将其转型为Class引用的类型。

3. 类型转换前先做检查

1.传统的类型检查,如shape,有RTTI确保类型转化的正确性,如果执行一个错误类型转化,抛出一个ClassCastException异常。
2.代表对象的类型的Class对象。
RTTI在java中还有第三种形式,是关键字instanceof。返回一个布尔值,告诉对象是不是某个特定类型的实例。例如:
类型信息
再将x转型为Dog前,if语句检查对象x是否从属于Dog类型。一般,可能想呀查找某种类型,这时可以轻松的使用instanceof技术所有对象。对于instanceof有比较严格的限制:只能将其与明明类型进行比较,不能与Class对象进行比较。

3.1 使用类字面常量

类型信息
类型信息

3.2 动态的instanceof

Class.isInstance提供一种动态测试对象的途径。于是所有单调的instanceof语句都可以从PetCount.java的例子中移除。例如:
类型信息
为了对所有的不同类型的Pet进行计数,PetCounterMap预加载LiteralCreator.allTypes中的类型。这使用了net.mindview.util.MapData类,这个类接收一个Iteralbe和一个常数值,然后用allTypes中的元素作为键,用0作为值填充Map。

3.3 递归计数

类型信息
count方法获取其参数的Class,然后使用isAssignableFrom执行运行时的检查,来校验传递的对象确实属于继承结构。countClass首先对该类的确切类型计数,然后如果其超类可以复制给baseType,countClass将其超类上递归计数。

5. instanceof与Class的等价性

在查询类型信息时,以instanceof的形式与直接比较Class对象的差别如下:
类型信息
类型信息
test方法使用两种形式的instanceof作为参数来执行类型检查,然后获取Class引用,并用==和equals检查Class对象是否相等。instanceof和isInstance生成的结果完全一样,equals和==也一样。