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 对象。为了使用类而做的准备工作实际包含三个步骤:

  1. 加载,这是由类加载器执行的。该步骤将查找字节码(通常在 classpath 所指定的路径中查找,但这并非是必须的),并从这些字节码中创建一个 Class 对象。
  2. 链接。在链接阶段将验证类中的字节码,为 static 字段分配存储空间,并且如果需要的话,将解析这个类创建的对其他类的所有引用。
  3. 初始化。如果该类具有超类,则先初始化超类,执行 static 初始化器和 static 初始化块。直到第一次引用一个 static 方法(构造器隐式地是 static)或者非常量的 static 字段,才会进行类初始化。

注意,对于被static final修饰的, 链接阶段不需要对其进行初始化就能读取, 而单static或者单final就不具备这一功能, 也就是在读取前,链接阶段需要重新为static字段去初始化存储空间

泛化的 Class 引用

Java 引入泛型语法之后,我们可以使用泛型对 Class 引用所指向的 Class 对象的类型进行限定。在下面的实例中,两种语法都是正确的:

  1. Class intClass = int.class;
  2. Class<Integer> genericIntClass = int.class;

Class<?> 这个?叫做通配符 表示“任何事物”

cast() 方法

  1. // typeinfo/ClassCasts.java
  2. class Building {}
  3. class House extends Building {}
  4. public class ClassCasts {
  5. public static void main(String[] args) {
  6. Building b = new House();
  7. Class<House> houseType = House.class;
  8. House h = houseType.cast(b);
  9. h = (House)b; // ... 或者这样做.
  10. }
  11. }

不常用

类型转换检测 - instanceof

  1. if(x instanceof Dog)
  2. ((Dog)x).bark();

一个动态 instanceof 函数

Class.isInstance()

  1. Parcel7 parcel7 = new Parcel7();
  2. System.out.println(Parcel7.class.isInstance(parcel7));
  3. 返回true

反射

RTTI跟反射的区别是,使用RTTI时编译器在编译时会打开并检验.class问你件,换句话说你可以使用”正常”的方式调用一个对象的所有方法,而反射,.class文件在编译的时候是不可用的,它由运行时的环境打开并检查

动态代理

代理是基本设计模式之一, 一个对方封装真实对象, 代替这个被封装的真是对象来提供其它或不同的操作— 这些操作经常涉及到与真实对象的通信, 因此代理对象经常充当中间传话的对象

可以通过调用静态方法 Proxy.newProxyInstance() 来创建动态代理 - 不常用

Mock 对象和桩

Mock对象和桩(Stub)在逻辑上都是Optional的变体, 他们都是最终程序中所使用的”实际对象”的代理,不过Mock对象跟桩都是假扮那些可以传递实际信息的实际对象, 而不是像Optional那样把包含潜在”null”的对象隐藏

Mock跟桩的区别是,Mock是轻量级的,适用于自测,通常为了解决不同的测试场景我们会创建出很多个Mock,但是桩是重量级的,可以在多个测试中被反复的使用,并且桩可以根据它被调用的方式,通过配置进行修改