运行时类型信息(RTTI, Runtime Type Identity)可以使得你在程序运行时发现和使用类型信息。
Java主要通过两种方式在运行时识别对象和类的信息:一种是传统的RTTI,它假定我们在编译时已经知道了所有的类型;另一种是反射机制,允许我们在运行时发现和使用类的信息。
Class对象包含了与类有关的信息。它用来创建类的所有常规的对象。Java使用Class对象来执行其RTTI。
类是程序的一部分,每个类都有一个Class对象。每当编写并且编译了一个新类酒会产生一个Class对象,它们被保存在同名的.class文件中。为了生成这个对象,运行在这个程序的Java虚拟机将使用被称为类加载器的子系统。类加载器子系统可以包含一条类加载链,但是只有以恶搞原生类加载器。原生类加载器加载的是可信类,包括Java API类,它们通常都是从本地盘加载的。
所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员引用时,就会加载这个类。这个证明构造器也是类的静态方法。类加载器首先检查这个类的Class对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找.class文件。在这个类的字节码被加载时,它们会接受验证,以确保器没有损坏或包含不良的代码。一旦某个类的.class对象被载入内存,它就被用来创建这个类的所有对象。Class对象只在需要的时候才被加载,static初始化时在类加载时进行的。
Class.forName("packeage.className")是取得Class引用的一种方法。它是用一个包含目标类的文本名的String作输入参数,返回的是一个Class对象的引用。对forName()的调用时为了产生副作用,如果className没被加载就加载它。如果没有找到className则会抛出ClassNotFoundException。
可以通过Object.getClass()方法来获取该对象的实际类型的Class引用。
Class.getName()来产生全限定的类名。Class.getSimpleName()获取不包含包名的类名。Class.getCanonicalName()产生全限定名。Class.isInterface()方法表示该对象是否时某个接口。Class.getSuperClass()查询该类的直接父类。Class.newInstance()方法是实现虚拟构造器的一种途径,虚拟构造器允许你声明我不知道你的确切类型,但是无论如何都要正确的创建你。使用newInstance()来创建的类必须带有默认的构造器。
Java可以使用类字面常量来生成对Class对象的引用。这样它在编译时就会收到检查。类字面常量不仅可以应用于普通的类,也可以应用于接口、数组以及基本数据类型。对于基本数据类型的包装器类,还有一个TYPE字段。TYPE字段是一个引用,指向对应的基本数据类型的Class对象。(相当于boolean.class === Boolean.TYPE;char.class === Character.TYPE....)
当使用.class来创建对Class对象的引用时,不会自动地初始化该Class对象。
创建一个对象包含三个步骤:
加载:由类加载器之行的。该步骤将查找字节码,并从这些字节码中创建一个Class对象
链接:在链接阶段将验证类中字节码,为静态域分配存储空间,并且如果必须的话,将解析这个类创建的对其他类的所有引用
初始化:如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。
初始化被延迟到了对静态方法或非常熟静态域进行首次引用时才执行。如果一个static final值是编译器常量,那么这个值可以直接读取,不需要对该类进行初始化,但是如果一个域设置为static final,对它的访问仍需要进行该类的初始化。
普通的类应用可以直接被重新赋值为指向任何其他的Class对象。通过使用泛型语法,可以让编译器强制执行额外的类型检查
Class intClass = int.class; Class<Integer> genericIntClass = int.class; intClass = double.class;// legal genericIntClass = double.class;//Illegal
为了在使用泛化的Class引用时放松限制,我们使用了通配符?表示任何事物
Class<?> class = int.class; class = double.class;
Class<?>有优先于平凡的Class,Class<?>表示你并非是碰巧伙食疏忽而使用了一个非具体的类的引用,而是你就是选择了非具体的版本。为了创建一个Class的引用,它被限定为某种类型或该类型的任何子类型,需要将通配符与extends关键字相结合,创建一个范围。
cast()方法接受参数对象,并将其转型为Class引用的类型。
Class.subClass()方法允许将一个类型转型为更加具体的类型。
Class.isInstance()提供了动态测试对象的途径。
Class.isAssginableFrom()在运行时检查该类是否在继承结构中。
基于构件的编程将使用与某种快应用开发(RAD)的应用构建工具,即集成开发环境来构建项目。
远程方法调用(RMI)允许一个java程序将对象分布到多台机器上。
Class类与java.lang.reflect类库一起对反射概念进行了支持,该类库包含了Field,Method以及Contructor类(每个类都实现了Member接口)。这些类型的对象由JVM在运行时创建的,用以表示未知类里对应的成员。可以使用Constructor创建新的对象,用get()和set()方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。还可以用getField(),getMethod()和getContructors()返回字段,方法和构造器对象的数组。Class的getMethod()方法和getConstructor()方法分别返回Method()对象的数组和Constructor对象的数组,这两个类都提供了用以解析其他对象所代表的方法来获取起名字,输入参数以及返回值。
RTTI和反射的区别在于:对于RTTI来说,编译器在编译时打开和检查.class文件;对于反射机制来说,.class文件在编译时是不可获取的,是在运行时打开和检查.class文件的。
代理提供额外的或不同的操作而插入的用来替代“实际”对象的对象。这些操作通常涉及与“实际”对象的通信,充当中间人的角色。
Java动态代理可以动态地创建代理并动态的处理对所代理方法的调用。在动态代理上所做的所有掉用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的对策。
Proxy.newProxyInstance()方法可以创建动态代理,此方法需要一个类的加载器,一个代理实现的接口列表以及InvocationHandler接口的一个实现。动态代理可以将所有调用重定向到调用处理器,因此通常会调用处理器的构造器传递给一个“实际”对象的引用,从而使得调用处理器在执行任务时,可以将请求转发。
InvocationHandler的invoke()方法接收代理对象。在invoke()内部,接口将被重定向委对代理的调用。通常会使用Method.invoke()将请求转发给被代理对象。
空对象
空对象可以接收传递给它的所代表的对象的消息,但是将返回表示为实际傻姑娘并不存在任何“真实”对象的值。通过这种方式,可以假设所有对象都是有效的。空对象最有用之处在于它更靠近数据。最简单的实现方式是创建一个标记接口。通常空对象都是单例
public interface Null{}
空对象的逻辑变体是模拟对象和桩。它们都表示在最终的程序中所使用的“实际”对象。模拟对象和桩只是假扮可以传递实际信息的存活对象,而不是像空对象那样可以成为null的一种更加智能化的替代物。模拟对象和桩之间的差异在于程度不同。模拟对象往往是轻量级和自测试的;桩只是返回桩数据,它通常是重量级的,并且经常在测试之间被复用。桩可以根据它们被调用的方式,通过配置进行修改。
interface的重要目标就是允许程序员隔离构件,进而降低耦合性。但是通过类型信息(instance of),这种耦合性还是会传播出去。最简单的方式是使用包访问权限。但是通过反射(getClass().getDeclaredMethod(methodName))仍可以调用所有方法,包括private。只要知道方法名,在Method对象上调用setAccessible(true)后调用invoke()即可。final域在遭遇修改时时安全的。运行时系统再不抛异常的情况下接受任何修改尝试,但实际上并不会发生任何修改