第十四章 类型信息

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


1 . class 对象

  • 事实上·,Class 对象就是用来创建类的所有的常规对象的。每当编写并且编译了一个新类,就会产生一个 Class 对象,(更恰当的说是被保存在一个同名的 .class 文件中)。为了生成这个类的对象,运行这个程序的 java 虚拟器(JVM) 将使用被称为 “ 类加载器 ” 的子系统。
  • 类加载器子系统实际上可以包含一条类加载器链,但是只有一个原生类加载器。
  • 所有的类都是在对其第一次使用的时候,动态加载到 JVM 中的。当程序创建第一个对类的静态成员的引用时,就会加载这个类。当时用 new 操作符的时候,也会被当做对类的静态成员的引用。
  • 所有的 Class 对象都属于 Class 类 。 forName() 是取得 Class 对象的引用的一种方法。用一个包含目标类的文本名的 String 作为输入参数,返回的是一个 Class 对象的引用。
  • 如果想在运行时使用类型信息,就必须首先获得对恰当的 Class 对象的引用, Class.forName() 就是实现此功能的便捷路径,因为你不需要为了获得 Class 引用而持有该类型的对象。如果有一个类型的对象,可以通过 getClass() 方法来获取 Class 引用。

    1. getName() 产生全限定类名
    2. getSimpleName() 产生不含包名的类名
    3. getCanonicalName() 全限定类名。

2. 类字面常量

  • 当时用 .class 来创建 Class 对象的时候,不会自动的初始化该 class 对象,准备工作有三个步骤:

    1. 加载,类加载器执行加载。
    2. 链接,为静态域分配存储空间,解析这个类创建的其他类的所有引用。
    3. 初始化,如果这个类有超类,对其进行初始化,执行静态初始化器和初始化块。
  • 初始化被延迟到了对静态方法或者非静态域进行首次引用时才执行。
  • 使用 .class 语法来获得对类的引用不会引发初始化,尽可能的实现了 “ 惰性 ” 。
  • Class 引用总是指向某个 Class 对象, Class 引用表示的就是它所指向的对象的确切类型,而该对象便是 Class 类的一个对象。

    Class<?> intClass = int.class
    // ? 表示任何事物  ,  Class<?> 优先级 高于 Class , 即使他们是等价的。
  • 为了创建一个 Class 引用,他被限定为某种类型,或该类型的任何子类型,你需要将通配符与 extends 关键字结合,创建一个范围:

    //xxx
    Class<? extends Number> bound = int.class ;
    bound = double.class
    bound = Number.class
    //xxx
  • 向 Class 引用添加泛型的语法的原因仅仅是为了提供编译期类型检查。
  • 当你将 泛型语法用于 Class 对象时,会发现:newInstance() 将返回该对象的确切类型。
  • Class 引用的转型语法:cast() 方法。(无用)

3. 类型转换前先做检查

  • 代表类型的 Class 对象,通过查询 Class 对象可以获取运行时所需的信息。
  • 关键字 instanceof 。返回一个布尔值,告诉我们对象是不是某个特定类型的实例。

    if(x instanceof Dog){
    ((Dog)x).bark();
    }
  • 在将 x 转型成一个 Dog 之前,上面的 if 会检查对象 x 是不是 Dog 类 。

4. 注册工厂

  • 使用工厂方法设计模式,将对象的创建工作交给类自己去完成。工厂方法可以多态的被调用,从而为你创建恰当类型的对象。

    //xxx
    public interface Factory(T){
    T create();
    }
    //xxx
  • 泛型参数 T 使得 create() 方法 可以在每种 Factory() 实现中返回不同类型,这也充分利用了协变返回类型。
  • 协变返回类型:导出类(子类)覆盖(即重写)基类(父类)方法时,返回的类型可以是基类方法返回类型的子类。

5. instanceof 与 Class 的等价性

  • 在查询类型信息时:以 instanceof 的形式(以 instanceof 的形式或 isInstance() 的形式,他们产生相同的结果 )与直接比较 Class 对象有一个重要的差别 。
  • instanceof 保持了类型的概念,它指的是 :” 你是这个类吗?或者你是这个类的派生类吗? “ ,如果用 == 比较实际的 Class 对象,他就没有考虑继承,它或者是这个确切的类型,或者不是。

6. 反射: 运行时的类信息

  • 如果不知道某个对象的确切类型, RTTI 可以告诉你,但是在编译时编译器必须知道所有要通过 RTTI 来处理的类。
  • 反射提供了一种机制,用来检查可用的方法,并返回其方法名。Java 通过了 java Beans 提供了基于构建的编程架构。
  • 在跨网络的远程平台上创建和运行对象的能力,被称为远程方法调用( RMI ) ,它允许一个 java 程序将对象分不到多台机器上。
  • Class 类与 java.lang.reflect 类库一起对反射的概念进行了支持。该类库包含了 Field 、 Method 、 Constructor 类(,诶各类都实现了 Member 接口) 。这样就可以使用 Constructor 类创建新的对象, 用 get() 、 set() 方法读取和修改与 Filed 对象关联的字段,用 invoke() 方法调用与 Method 对象关联的方法。另外,还可以调用 getFields() 、getMethods() 和 getConstructors() 等便利的方法,以返回表示字段、方法、构造器的对象的数组。这样,匿名对象的类信息就能在运行时被完全确定下来,而在编译时不需要知道任何的事情。
  • 对于 RTTI 来说,编译器在编译时打开和检查 .class 文件。换句话说,我们可以用 “ 普通 ” 方式调用对象的所有方法,而对于反射机制来说, .class 文件在编译时是不可获取的,所以是在运行时打开和检查 .class 文件。
  • 判断某个特定的签名特征是否与我们的目标字符串相等。(使用 indexOf() 方法)
  • 用一个额外的 char 、int 、String 参数来调用 java ShowMethods java.lang.String 。在编程时,如果是不记得一个类是否包含某个方法,,这是反射可以节省很大时间。

7. 动态代理

  • 代理是基本的设计模式之一,他是你为了提供额外的或不同的操作,而插入的用来代替 “ 实际 ” 对象的对象。这些操作通常涉及与 “ 实际 ” 对象的通信,因此代理通常充当着中间人的角色。
  • java 可以 动态的创建代理并动态的处理对所代理方法的调用,在动态代理上所做的所有调用都会被重定向到单一的 调用处理器 上。
  • 通过调用静态方法 Proxy.newProxyInstance() 可以创建动态代理。这个方法需要得到一个类加载器( 可以从已经被加载的对象中获取其类加载器,然后传递给他 ) , 一个你希望该代理实现的接口列表 , 以及 该接口的一个实现。
  • 通常,你会执行某些被代理的操作,然后使用 Method.invoke() 将请求转发给被代理对象,并传入必需的参数。

由本人从 Thinking in java ( java 编程思想 ) 整理而来

最后修改:2018 年 07 月 15 日
哇卡哇卡