《Java编程思想》 第十四章 类型信息 记录

  • 运行时类型信息使得你可以在程序运行时发现和使用类型信息。
  • 它使你从只能在编译期执行面向类型的操作的禁锢中解脱出来,并且可以使用某些非常强大的程序。
  • RTTI,它假定我们在编译时已经知道了所有类型;“反射”机制,它允许我们在运行时发现和使用类信息

1. 为什么需要RTTI

  • 当从数组中取出元素时,这种容器,实际上它将所有的事物都当作Object持有,会自动将结果转型回基类。这是RTTI最基本的使用形式,因为在Java中,所有的类型转换都是在运行时进行正确性检查的。这也是RTTI名字的含义:在运行时,识别一个对象的类型。
  • 在编译时,将由容器和Java的泛型系统来强制确保这一点;而在运行时,由类型转换操作来确保这一点。
  • 通常希望大部分代码尽可能少地了解对象的具体类型,而是只在对象家族的一个通用表示打交道。这样代码会更容易写,更容易读,且更便于维护;设计也更容易实现、理解和改变。所以“多态”是面向对象编程的基本目标。

2. Class对象

  • Java的类型信息在运行时通过Class对象的特殊对象表示。它包含了与类有关的信息。事实上,Class对象就是用来创建类的所有的“常规”对象的。Java使用Class对象来执行其RTTI,即使你正在执行的是类似转型这样的操作。Class类还拥有大量的使用RTTI的其他方式。
  • 类是程序的一部分,每个类都有一个Class对象。每当编写并且编译了一个新类,就会产生一个Class对象(更恰当的说,是被保存在一个同名的.class文件中)。为了生成这个类的对象,运行这个程序的Java虚拟机(JVM)将使用被称为“类加载器”的子系统。
  • 类加载器子系统实际上可以包含一条类加载器链,但是只有一个原生类加载器,它是JVM实现的一部分。原生加载器加载的是所谓的可信类,包括Java API类,它们通常是从本地盘加载的。在这条链中,通常不需要添加额外的类加载器,但是如果你有特殊需求(例如以某种特殊的方式加载类,以支持Web服务器应用,或者在网络中下载类),那么你有一种方式可以挂接额外的类加载器。
  • 所有的类都是在对其第一次使用时,动态加载到JVM中。当程序创建第一个对类的静态成员的引用时,就会加载这个类。这个证明构造器也是类的静态方法,即使在构造器之前并没有使用static关键词。因此,使用new操作符创建类的新对象也会被当作对类的静态成员的引用。
  • Java程序在它开始运行之前并非被完全加载,其各个部分是在必需时才加载的。
  • 类加载器首先检查这个类的class对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找.class文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良Java代码(这是Java中用于安全防范目的的措施之一)。
  • Class对象仅在需要的时候才会加载,static初始化时在类加载时进行的。
  • 无论何时,只要你想要运行时使用类型信息,就必须首先获得对恰当的Class对象的引用。
  • Class的newInstance()方法是实现“虚拟构造器”的一种途径,虚拟构造器允许你声明:“我不知道你的确切类型,但是无论如何要正确地创建你自己。”
  • 使用newInstance()来创建的类,必须带有默认的构造器。
  • 除了Class.forName(),还可以使用类名.class来获取Class对象的引用。
  • 当使用“.class”来创建对Class对象的引用时,不会自动地初始化该Class对象。为了使用类而做地准备工作实际包含三个步骤:
  1. 加载,这是由类加载器执行的。该步骤将查找字节码(通常在classpath所指定的路径中查找,但这并非是必需的),并从这些字节码中创建一个class对象
  2. 链接,在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必需的话,将解析这个类创建的对其他类的所有引用
  3. 初始化。如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块
  • 初始化被延迟到了对静态方法(构造器隐式地是静态的)或者非常数静态域进行首次引用时才执行。
  • 仅使用.class语法来获得对类的引用不会引发初始化。但是,为了产生Class引用,Class.forName()立即就进行了初始化。
  • 如果一个static final值是“编译器常量”,那么这个值就不需要对类进行初始化就可以被读取。但是,如果只是将一个域设置为static和final的,还足以确保这种行为,如获取随机值,会强制进行类的初始化。
  • 如果一个static域不是final的,那么在对它访问时,总是要求在它被读取之前,要先进行链接(为这个域分配存储空间)和初始化(初始化该存储空间)。
  • Class引用总是指向某个Class对象,它可以制造类的实例,并包含可作用于这些实例的所有方法代码。它还包括该类的静态成员,因此,Class引用表示的就是它所指向的对象的确切类型,而该对象便是Class类的一个对象。
  • 普通的类引用不会产生警告信息,泛型类引用只能赋值为指向其声明的类型,普通的类引用可以被重新赋值为指向任何其他的Class对象。通过使用泛型语法,可以让编译器强制执行额外的类型检查。
  • 为了在使用泛化的Class引用时放松限制,可以使用通配符,它是Java泛型的一部分。通配符就是“?”,表示“任何事物”。
  • 向Class引用添加泛型语法的原因仅仅是为了提供编译期类型检查,因此如果你操作有误,稍后立即就会发现这一点。在使用普通Class引用,你不会误入歧途,但是如果你确实犯了错误,那么直到运行时你才会发现它,而这显得很不方便。
  • cast()方法接受参数对象,并将其转换为Class引用的类型。

3. 类型转换前先做检查

  • RTTI形式包括:
  1. 传统的类型转换,由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换,就会抛出一个ClassCastException异常。
  2. 代表对象的类型的Class对象。通过查询Class对象可以获取运行时所需的信息。
  • 关键字instanceof,它返回一个布尔值,告诉我们对象是不是某个特定类型的实例。
  • 对instanceof有比较严格的限制:只可将其与命名类型进行比较,而不能与Class对象作比较。
  • Class.inInstance()方法提供了一种动态地测试对象的途径。
  • instanceof保持了类型的概念,它指的是“你是这个类吗,或者你是这个类的派生类吗?”而如果用==比较实际的Class对象,就没有考虑继承,它或者是这个确切的类型,或者不是。

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

  • 希望提供在跨网络的远程平台上创建和运行对象的能力,被称为远程方法调用(RMI),它允许一个Java程序将对象分布到多台机器上。
  • Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了Field、Method以及Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。这样你就可以使用Constructor创建新的对象,用get()和set()方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。另外,还可以调用getField()、getMethods()和getConstructors()等很便利的方法,已返回表示字段、方法以及构造器的对象的数组。这样,匿名对象的类信息就能在运行时被完全确定下来,而在编译时不需要知道任何事情。
  • RTTI和反射之间真正的区别只在于,对RTTI来说,编译期在编译时打开和检查.class文件。(换句话说,我们可以用“普通”方法调用对象的所有方法。)而对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。
  • Class.forName()生成的结果在编译时是不可知的,因此所有的方法特征签名信息都是在执行时被提取出来的。

7. 动态代理

  • 代理是基本的设计模式之一,它是你为了提供额外的或不同的操作,而插入的用来代替“实际”对象的对象。这些操作通常涉及与“实际”对象的通信,因此代理通常充当着中间人的角色。
  • Java的动态代理比代理的思想更向前一步,因为它可以动态地创建代理并动态地处理对所代理的方法的调用。
  • 通过调用静态方法Proxy.newProxyInstance()可以创建动态代理,这个方法需要得到一个类加载器(你通常可以从已经被夹在的对象中获取其类加载器,然后传递给它),一个你希望该代理实现的接口列表(不是类或抽象类),以及InvocationHandler接口的一个实现。
  • 使用动态代理来编写一个系统以实现事务,其中,代理在被代理的调用执行成功时(不抛出任何异常)执行提交,而在其执行失败时执行回滚。你的提交和回滚都针对一个外部的文本文件,该文件不在Java异常的控制范围之内。你必须注意操作的原子性。

8. 空对象

  • 模拟对象和桩之间的差异在于程度不同。模拟对象往往是轻量级和自测试的,通常很多模拟对象被创建出来是为了处理各种不同的测试情况。桩只是返回桩数据,它通常是重量级的,并且经常在测试之间被复用。桩可以根据它们被调用的方式,通过配置进行修改,因此桩是一种复杂对象,它要做很多事情。然而对于模拟对象,如果你需要做很多事情,通常会创建大量小而简单的模拟对象。

9. 接口与类型信息

  • interface关键词的一种重要目标就是允许程序员隔离构件,进而降低耦合性。如果你编写接口,那么就可以实现这一目标,但是通过类型信息,这个耦合性还是会传播出去,接口并非是对解耦的一种无解可击的保障。

10. 总结

  • RTTI允许通过匿名基类的引用来发现类型信息。
  • 面向对象编程语言的目的是让我们在凡是可以使用的地方都使用多态机制,只在必需的时候使用RTTI。
发布了57 篇原创文章 · 获赞 11 · 访问量 9875

猜你喜欢

转载自blog.csdn.net/qq_36160730/article/details/97683917