本章将讨论Java是如何让我们在运行时识别对象和类的信息的
1.传统的RTTI,它假定在编译时已经知道了所有的类型
2.反射,他允许我们在运行时发现和使用类的信息
14.1 为什么需要RTTI
将抽象基类中toString()方法声明为Object可以强制继承者覆写该方法,并可以防止对无实际类型的抽象基类的使用
14.2 Class对象
类型信息在运行时是通过Class对象来完成的,它包含了与类有关的信息,Java使用Class对象来实现RTTI
类是程序的一部分,每个类都有一个Class对象(被保存在.class文件中)。为了生成这个类的对象,运行这个程序的Java虚拟机(JVM)将使用“类加载器”的子系统。
类加载器子系统实际上可以包含一条类加载器链,但是只有一个原生类加载器,它是JVM实现的一部分。
所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员的引用时,就会加载这个类,这个证明构造器也是静态的。
一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。
Class对象仅在需要的时候才被加载,static初始化是在类加载时进行的
无论何时,只要你想在运行时使用类型信息,就必须首先获得对恰当的Class引用,Class.forName()就是实现此功能的便捷途径,因为你不必为了获得该类型的引用而持有该类型的对象。但是如果已经有了该类型的对象,那么就可以用getClass()方法类获取该类型的引用了,这个方法隶属于Object基类
Class的newInstance()方法是实现“虚拟构造器”的一种途径,虚拟构造器允许你声明“我不知道你的确切类型,但请你正确的创建自己”。(该类型必须有默认构造器)
14.2.1 类字面常量(推荐)
Java还提供了“类字面常量”来获取Class对象的引用:FancyTory.class
这样做不仅更加简单,而且更加安全,因为它在编译期受检查(不需要置于try-catch块中),并且根除了对forName()方法的调用,所以更加高效。
当使用“.class”来创建对Class对象的引用时,不会自动地初始化该Class对象,而使用forName()就会初始化类,而使用类要做的准备工作实际上有三步:
- 加载:由类加载器创建Class对象
- 链接:为静态域分配空间,和对其他类的引用
- 初始化:对类和类的超类初始化,初始化被推迟到对静态的使用
14.2.2 泛化的Class引用
Class引用表示的就是它所指向的对象的确切类型,而该对象便是Class类的一个对象。
14.2.3 新的转型语法
14.3 类型转换前先做检查
使用instanceof()来确定一个对象是不是一个类型的实例(只可将其余命名类型进行比较,而不能与Class对象做比较)
14.3.1 使用类字面常量
14.3.2 动态的instanceof
instanceof用于静态判断也就是说对象和类型在编写代码时必须给出,
Class.isinstance()方法用于动态类型测试
14.3.3 递归计数
14.4 注册工厂
14.5 instanceof与Class的等价性
在查询类型信息时直接使用Class对象来比较产生的结果与使用Instanceof或者isinstance()不一样,直接使用Class对象来比较不会考虑到继承的问题,是就是,不是就不是,而另外两个会将子类视作父类
14.6 反射:运行时的类信息
RTTI与反射之间的真正区别只在于,对RTTI来说,编译器在编译时打开和检查.class文件(换句话说,我们可以用“普通”方式调用对象的所有方法)。而对反射机制来说,.class文件在编译时是不可得的,所以是在运行时打开和检查.class文件
14.6.1 类方法提取器
14.7 动态代理
任何时候你想在调用一个方法的前后做出一些额外操作,而又想将这些额外操作分离到不同地方,这时代理就很有用
在动态代理上所做的所有调用都会被重定向到单一的调用处理器上
Proxy.newProxyInstance可以创建动态代理,这个方法需要得到一个类加载器(通常可以从已加载的对象上获取其类的加载器),还有你希望代理的接口列表(不是抽象类或类),以及InvocationHandler接口的一个实现。
14.8 空对象
当你是用内置的null时表示缺少对象,那么在每次使用引用时都必须先测试现在该对象是不是null,这就很麻烦。
空对象一般是这样实现的public interface Null{}
,然后再对一个要使用的对象创建一个子类,如:class NullPerson extends Person implements Null{ }
然后在子类构造器中将各个属性值设为null
14.8.1 模拟对象与桩
类似于空对象,只是模拟对象与桩都是假扮可以传递实际信息的存活对象。
14.9 接口与类型信息
几乎没有任何方式可以阻止反射到达并调用那些非公共访问权限的方法,对于域来说,即使是private域。
14.10 总结