运行时类型信息使得你可以在程序运行时发现和使用类型信息,它使你从只能在编译期执行面向类型的操作的禁锢中解脱了出来。
Java让我们在运行时识别对象和类的信息的两种方式:一种是传统的RTTI,它假定我们在那编译时已经知道了所有的类型;另一种是反射机制,它允许我们在运行时发现和使用类的信息。
通常你希望大部分代码尽可能少地了解对象的具体类型,而是只与对象家族中的一个通用表示打交道,这样代码更容易写,更容易读,且更便于维护,所以多态是面向对象编程的基本目标。但是假如你碰到一个特殊的编程问题--如果能够知道某个泛化引用的确切类型,就可以使用更简单的方式去解决它。(RTTI涵盖多态,也支持对多态进行补充以提供灵活的应用,这个概念是作者从C++延续过来的,读者可以不用太纠结词法,理解其设计初衷、实现原理、使用场景即可)
RTTI形式包括:
- 传统的类型转换,如Shape,由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换,就会抛出一个ClassCastException异常。
- 代表对象的类型的Class对象,通过查询Class对象可以获取运行时所需的信息。
- instanceof关键字,判断对象是不是某个特定类型的实例
Class对象
Class对象包含了与类有关的信息,是用来创建类的所有的”常规“对象的,每当编写并编译了一个新类,就会产生一个Class对象,由类加载器对其进行支持(JVM相关知识后面有机会我会跟朋友们分享一下)。
类加载
所有的类都是在对其第一次使用时,动态加载到JVM中的,当程序创建第一个对类的静态成员的引用时,就会加载这个类,这个证明构造器也是类的静态结构,即使在构造器之前并没有使用static关键字。类加载器首先检查这个类的Class对象是否已经加载,如果尚未加载,会根据类名查找.class文件,然后加载字节码并创建一个CLass对象,一旦某个类的Class对象被载入内存,他就被用来创建这个类的所有对象。
类对象引用
无论何时,只要你想在运行时使用类型信息,就必须首先获得对恰当的Class对象的引用,Class.forName()就是实现此功能的便捷途径,因为你不需要为了获得Class引用而持有该类型的对象,当然有了也可以通过继承Object的getClass()方法获取Class引用。
Class的newInstacne()方法是实现虚拟构造器的一种途径,虚拟构造器允许你声明“我不知道你的确切类型,但是无论如何要正确地创建你自己”,因为使用的只是一个Class引用,编译期也不具备任何更进一步的类型信息,当你创建新实例时,会得到Object引用,在你可以发送Object能够接受之外的任何消息之前,你必须更多地了解它,并执行某种转型。另外newInstance()要求这个类必须带有默认构造器。
Class up = c.getSuperClass();
Object obj = up.newInstance():
Java还提供了类字面常量来生成对Class对象的引用
FancyToy.class
这样做不仅更简单,而且更安全,因为它在编译时就会受到检查,并且它根除了对forName()方法的调用,不会自动地初始化该Class对象,初始化被延迟到了对静态方法(构造器隐式是静态的)或者非常数静态域进行首次引用时才执行,所以也更高效。类字面常量不仅可以应用于普通的类,也可以应用于接口、数组以及基本数据类型。
泛化的Class引用
Java允许你对Class引用所指向的Class对象的类型进行限定,通过实用泛型语法,可以让编译器强制执行额外的类型检查。
public class GenericClassReferences {
public static void main(String[] args) {
Class intClass = int.class;
CLass<Integer> genericIntClass = int.class
genericIntClass = Integer.class
intClass = double.class;
// genericIntClass = double.class // Illegal
}
}
为了在使用泛化的Class引用时放松限制,可以使用通配符?表示“任何事物”。
public class WildcardClassReferences {
public static void main(String[] args) {
Class<?> intClass = int.class;
intClass = double.class;
}
}
Class优于平凡的Class,即便它们是等价的,平凡的Class不会产生编译器告警信息,Class的好处是它表示你并非是碰巧或者由于疏忽,而使用了一个非具体的类引用,你就是选择了非具体的版本(是显式使用倾向)。
类型检查
instanceof
在类型向下转型前,可以通过类型检查提供转型的安全度与精确度。
if (x instanceof Dog) {
((Dog)x).bark();
}
isInstance
对instanceof有比较严的限制:只可将其与命名类型进行比较,而不能与Class对象作比较。Class.isInstance方法提供了一种动态地测试对象的途径。
public void count(Pet pet) {
for (Map.Entry<Class<? extends Pet>, Integer> pair : entrySet()) {
if (pair.getKey().isInstance(pet)) {
put(pair.getKey(), pair.getValue() + 1);
}
}
}
isAssignableFrom
Class.isAssignableFrom()允许对有继承关系的类型上进行类型判断。
public void count(Object obj) {
Clss<?> type = obj.getClass();
if (!baseType.isAssignableFrom(type)) {
throw new RuntimeException();
}
countClass(type):
}
private void count(Class<?> type) {
Integer quantity = get(type);
put(type, quantity == null ? 1 : quantity + 1);
Class<?> superClass = type.getSuperClass();
if (superClass != null && baseType.isAssignableFrom(superClass)) {
countClass(superClass):
}
}
反射
RTTI有一个限制:在编译时编译器必须知道所有要通过RTTI来处理的类。如果获取了一个指向某个并不在你程序空间中的对象的引用,在编译时你的程序根本没法获知这个对象所属的类,比如磁盘文件和远程网络的字节流。
Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了Field、Method以及Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员,这样匿名对象的类信息就能在运行时被完全确定下来,而在编译时不需要知道任何事情。
public clss ShowMethods {
public static void main(String[] args) {
// ignore arguments check and exception catch
Class<?> c = Class.forName(args[0]);
Method[] methods = c.getMethods();
Constructor[] ctors = c.getConstructors();
// extract method and constructor info
}
}
Class.forName()生成的结果在编译时是不可知的,因此所有的方法特征签名信息都是在执行时被提取出来的,反射机制提供了足够的支持,使得能够创建一个在编译时完全未知的对象,并调用此对象的方法。
通过使用反射,仍旧可以到达并调用所有方法,甚至是private方法,如果知道方法名,你就可以在其Method对象上调用serAccessible(true)。对域来说也是一样不过final除外,final域实际上在遭遇修改时死安全的,运行时系统会在不抛异常的情况下接受任何修改尝试,但是实际上不会发生任何修改。(实际应用场景有动态配置项目,比如apollo)
RTTI和反射之间真正的区别在于,对RTTI来说,编译器在编译时打开和检查.class文件,而对于反射机制来说.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。
动态代理
在任何时刻,只要你想将额外的操作从实际对象中分离到不同的地方,特别是当你希望能够很容易地做出修改,从没有使用额外操作转为使用这些操作,或者反过来时,代理就显得很有用。Java的动态代理比代理的思想更向前迈进了一步,因为它可以动态地创建代理并动态地处理对所代理方法的调用,在动态代理商所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的对策。
class DynamicProxyHandler implements InvocationHandler {
private Object proxied;
public DynamicProxyHandler(Object proxied) {
this.proxied = proxied;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy: " + proxy.getClass() + ", method: " + method + ", args: " + args);
return method.invoke(proxied, args);
}
}
class SimpleDynamicProxy {
public static void consumer(Interface iface) {
iface.doSomething();
iface.somethingElse("bonobo");
}
public static void main(String[] args) {
RealObject real = new RealObject();
consumer(real);
Interface proxy = (Interface)Proxy.newProxyInstance(Interface.class.getClassLoader(),
new Class[]{Interface.class},
new DynamicProxyHandler(real));
consumer(proxy);
}
}