运行时类型信息使得你可以在程序运行时发现和使用类型信息。
Java让我们能在运行时识别对象和类的信息的两种方式:
- 传统的RTTI:它假定我们在编译时已经知道了所有的类型;
- 反射机制:它允许我们在运行时发现和使用类信息。
1.为什么需要RTTI
-
当把
Shape
对象放入List<Shape>
的数组时会向上转型。但在向上转型为Shape
的时候也丢失了Shape对象
的具体类型。对于数组而言,它们只是Shape
类的对象。当从数组中取出元素时,这种容器—实际上他将所有的事物当作
Object
持有—会自动将结果转型回Shape
。这是RTTI最基本的使用形式,因为在Java中,所有类型转换都是在运行时进行正确性检查的。接下来就是多态机制的事情了,
Shape
对象实际执行什么样的代码,是由引用所指向的具体对象Circle
、Square
或Triangle
而决定的。你希望大部分代码尽可能地了解对象的具体类型,而是只与对象家族的一个通用表示打交道。
2.Class对象
Class对象就是用来创建类的所有的常规对象。
类似程序的一部分,每一个类都有一个Class对象。为了生成这个类的对象,运行这个程序的Java虚拟机将使用被称为 类加载器
的子系统。
Class.forName()
可以获取Class对象的引用。getName()
产生全限定的类名,并分别使用 getSimpleName()
和getCanonicalName()
来产生不含包名的类名和全限定名。isInterface()
可以告诉你这个Class对象是否表示某个接口。还可以使用 getSuperclass()
方法查询其直接基类。
2.1 类字面常量
-
使用字面常量也可以生成对Class对象的引用
FancyToy.class
类字面常量不仅可以应用于普通的类,也可以应用于接口、数组以及基本数据类型。
当使用
.class
来创建对Class对象的引用时,不会自动地初始化该Class对象。为使用类而做的准备工作实际包含三个步骤:- 加载:这是由类加载器执行的;
- 链接:在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必须的话,将解析这个类创建的对其他类的所有引用。
- 初始化:如果该类具有超类,则对其初始化,执行静态初始化其和静态初始化块。
-
Class.forName()
会立即执行初始化;
2.2 泛化的Class引用
-
Class引用总是指向某个Class对象,它可以制造类的示例,并包含可作用与这些实例的所有方法代码。它还包含该类的静态成员,因此,Class引用表示的就是它所指向的对象的确切类型。而该对象便是Class类的一个对象。
尽管泛型类引用只能赋值为指向其声明的类型,但是普通类引用可以被重新赋值为指向任何其他的Class对象。通过泛型语法可以让编译器强制执行额外的类型检查。
-
使用通配符
?
放松限制,?
代表任何事物。Class<?> intClass = int.class;
限定创建Class引用的范围
Class<? extends Numner> bounded = int.class; Class<? super FancyToy> up = ftClass.getSuperclass();
-
newInstance()
返回的不是精确类型,而只是Object。
2.3 新的转型语法
-
cast()
用于Class引用的转型语法,新的转型语法对于无法使用普通转型的情况显得非常有用。
3.类型转换前先做检查
RTTI的第三种形式,就是关键字 instanceof
。它返回一个布尔值,告诉我们对象是不是某个特定类型的实例。
3.1 使用类字面量
3.2 动态的 instanceof
-
Class.isInstance
方法提供了一种动态地测试对象的途径。
3.3 递归计数
4 注册工厂
- 使用
工厂方法
设计模式,将对象的创建工作交给类自己去完成。
5.instanceof与Class的等价性
6.反射:运行时的类信息
- 如果不知道某个对象的确切类型,RTTI可以告诉你。但这个类型在编译时必须已知。
- 人们想要在运行时获取类的信息的另一动机,便是希望提供在跨网络的远程平台上创建和运行对象的能力。这种被称为远程方法调用,它允许一个Java程序将对象分布到多台机器上。
- RTTI和反射之间真正的区别在于:对于RTTI来说,编译器在编译时打开和检查
.class
文件。而对于反射机制来说,在运行时打开和检查.class
文件。
7.动态代理
- Java的动态代理比代理的思想更向前迈进一步,因为它可以动态创建代理并动态地处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作室揭示调用的类型并确定相应的对策。
8.空对象
空对象最有用支出在于它更靠近数据,因为对象表示的是问题空间内的实体。
8.1 模拟对象与桩
-
空对象的逻辑变体是模拟对象和桩。与空对象一样,它们都表示在最终的程序中所使用的实际对象。
模拟对象和桩之间的差异在于程度不同。模拟对象往往是轻量级和自测试的,通常很多模拟对象被创建出来是为了处理各种不同的测试情况。桩只是返回桩数据,它通常是重量级的。
9.接口与类型信息
- interface关键字的一种重要目标就是允许程序员隔离构建,进而降低耦合性。如果你编写接口,那么就可以实现这一目标,但是通过类型信息,这种耦合性还是会传播出去——接口并非是对解耦的一种无懈可击的保障。最简单的解决方式是对实现使用包访问权限。
06/06/2019 :created
网友评论