RTTI(RunTime Type Information,运行时类型信息)能够在程序运行时发现和使用类型信息
为什么需要 RTTI
从这个图可以看出来集成关系,他们都继承shape. 通过向上转型, 不论是圆,或者三角都可以向上转型为shape.这时我们怎么知道这个shape到底是哪个形状呢?这个时候RTTI就可以告诉我们它到底是那个类型
跟它相似的概念是动态绑定,也是在程序运行的过程中的能力,只是动态绑定针对的是方法, 举例来说, 圆,三角都继承了shape,进而重写了draw()的方法,当它们向上转型的时候,代码是写成shape.draw()这种形式, 编译器怎么知道要调用哪个类型呢, 而动态绑定就解决了这一问题,它是java自带的功能. 它的实现原理是:当类被加载到虚拟机中,在方法区保存元数据,其中有一个叫做方法表的东西, 记录了这个类定义的方法指针, 每一个表指向一个具体的方法代码,如果父类的代码被子类重写,那么这个表指向子类代码实现处, 从父类继承的方法代码位于子类方法代码的前面
与动态绑定相对于的是静态绑定,静态绑定指的就是那些被final, static,类型信息, 这些在类创建的时候就被加载到方法区中,不需要代码运行就能访问的 ,就叫做静态绑定
Class 对象
要理解RTTI在java中的运行原理,就必须要知道类型信息在运行时是如何体现的, 这项工作是由class对象的特殊对象来完成的,它包含了与类有关的信息,实际上class对象就是用来创造该类所有的常规对象的, java运用class对象来实现RTTI, 甚至像类型转换这种操作,都是通过class对象实现的
类是java程序中的一部分,每个类都有一个class对象,保存在.class文件中,为了生成这个类的对象,JVM会调用”类加载器”子系统把这个类加到内存中去
所有的类都是第一次使用时被加载到JVM中的, 当程序创建一个对类的静态成员的引用时,就会加载这个类
其实构造器也是类的静态方法,虽然构造器前面并没有 static
关键字。所以,使用 new
操作符创建类的新对象,这个操作也算作对类的静态成员引用。
因此, Java程序在开始运行的时候并没有被完全加载, 很多部分都是在运行的时候才被加载的, 这就叫做动态加载, 动态加载也就使得Java语言具有很多其他静态语言不具备的特性
类加载器首先会检查这个class对象的类是否已经被加载,如果尚未被加载,默认的类加载器就会根据类名查找.class文件,这个类的.class文件被jvm加载后,JVM会对其进行验证, 确保它没有损坏,并且不包含不良的java代码,然后调用类加载器子系统把这个类的class对象加载到内存中去,一旦这个class对象被加载到内存中, 就可以创建这个类的所有对象了
使用类名.class来创建对象
当使用 .class
来创建对 Class
对象的引用时,不会自动地初始化该 Class
对象。为了使用类而做的准备工作实际包含三个步骤:
- 加载,这是由类加载器执行的。该步骤将查找字节码(通常在 classpath 所指定的路径中查找,但这并非是必须的),并从这些字节码中创建一个
Class
对象。 - 链接。在链接阶段将验证类中的字节码,为
static
字段分配存储空间,并且如果需要的话,将解析这个类创建的对其他类的所有引用。 - 初始化。如果该类具有超类,则先初始化超类,执行
static
初始化器和static
初始化块。直到第一次引用一个static
方法(构造器隐式地是static
)或者非常量的static
字段,才会进行类初始化。
注意,对于被static final修饰的, 链接阶段不需要对其进行初始化就能读取, 而单static或者单final就不具备这一功能, 也就是在读取前,链接阶段需要重新为static字段去初始化存储空间
泛化的 Class
引用
Java 引入泛型语法之后,我们可以使用泛型对 Class
引用所指向的 Class
对象的类型进行限定。在下面的实例中,两种语法都是正确的:
Class intClass = int.class;
Class<Integer> genericIntClass = int.class;
Class<?> 这个?叫做通配符 表示“任何事物”
cast()
方法
// typeinfo/ClassCasts.java
class Building {}
class House extends Building {}
public class ClassCasts {
public static void main(String[] args) {
Building b = new House();
Class<House> houseType = House.class;
House h = houseType.cast(b);
h = (House)b; // ... 或者这样做.
}
}
不常用
类型转换检测 - instanceof
if(x instanceof Dog)
((Dog)x).bark();
一个动态 instanceof
函数
Class.isInstance()
Parcel7 parcel7 = new Parcel7();
System.out.println(Parcel7.class.isInstance(parcel7));
返回true
反射
RTTI跟反射的区别是,使用RTTI时编译器在编译时会打开并检验.class问你件,换句话说你可以使用”正常”的方式调用一个对象的所有方法,而反射,.class文件在编译的时候是不可用的,它由运行时的环境打开并检查
动态代理
代理是基本设计模式之一, 一个对方封装真实对象, 代替这个被封装的真是对象来提供其它或不同的操作— 这些操作经常涉及到与真实对象的通信, 因此代理对象经常充当中间传话的对象
可以通过调用静态方法 Proxy.newProxyInstance()
来创建动态代理 - 不常用
Mock 对象和桩
Mock对象和桩(Stub)在逻辑上都是Optional的变体, 他们都是最终程序中所使用的”实际对象”的代理,不过Mock对象跟桩都是假扮那些可以传递实际信息的实际对象, 而不是像Optional那样把包含潜在”null”的对象隐藏
Mock跟桩的区别是,Mock是轻量级的,适用于自测,通常为了解决不同的测试场景我们会创建出很多个Mock,但是桩是重量级的,可以在多个测试中被反复的使用,并且桩可以根据它被调用的方式,通过配置进行修改