14.2java
RTTI运行时类型识别。数据库
Class对象包含了与类有关的信息,Java使用Class对象来执行其RTTI。每一个类都有一个Class对象,每当编写而且编译了一个新类,就会产生一个Class对象。为了生成这个类的对象,JVM使用类加载器子系统。类加载器子系统能够包含一条类加载器链:编程
全部的类都是在对其第一次使用时,动态加载到JVM中的。当程序建立第一个对类的静态成员引用时,就会加载这个类。注意构造函数也是类的静态方法。使用new操做符建立类的新对象也会被看成对类的静态成员的引用。数组
所以,Java程序在它开始运行以前并不是被彻底加载,其各个部分是在必需时才加载的,C++是静态加载。安全
类加载器首先检查这个类的Class对象是否已经加载,若是还没有加载,默认的类加载器就会根据类名检查.class文件。这个类的字节码被加载时,它们会接受验证以确保其没有被破坏,而且不包含不良的Java代码。一旦某个类的Class对象被载入内存,它就被用来建立这个类的全部对象。函数
Class类的静态方法forName()接受目标类的名字为参数获取该类Class对象的引用。若是找不到要加载的类会抛出ClassNotFoundException。不管什么时候,只要你想在运行时使用类型信息,就必须首先得到恰当的Class对象的引用。Class.forName()是获取Class对象引用的便捷途径,由于不须要为了得到Class引用而持有该类型的对象。可是,若是已经有了一个该对象的引用,能够经过调用它的getClass()方法来获取Class对象的引用了,这个方法属于Object的一部分。第三种生成Class对象引用的方法是类字面常量,例如:工具
FancyToy.class;spa
这样作不只简单并且更安全,由于它在编译时就会受到检查,所以也不会抛出异常。类字面常量不只能够应用于普通的类,也能够应用于接口、数组以及基本数据类型。另外对于基本数据类型的包装器类还有一个标准字段TYPE。它是指向对应基本数据类型的Class对象,例如:翻译
boolean.class 与Boolean.TYPE等价。设计
做者建议使用boolean.class来保持与普通类的一致性。
做者提到奇怪的一点是,Class.forName()获得Class的对象会使得此类进行初始化步骤(此处说的初始化步骤并非指初始化Class的对象,关于使用类而准备的三个步骤见下文),而字面常量**.class获得Class的对象并不会使此类进行初始化步骤。
为了使用类而作的准备工做实际包含三个步骤:
(1)加载,由类加载器执行,该步骤查询字节码(一般在classpath所指定的路径中查找),并从这些字节码中建立一个Class对象;
(2)连接,此阶段将验证类中的字节码,为静态域分配存储空间,而且若是必须的话,将解析这个类建立的对其余类的全部引用;
(3)初始化,若是该类具备超类,则对其初始化,执行静态初始化函数和静态初始化快。初始化被延迟到了对静态方法或者很是数静态域进行首次引用时才执行。
例子的总结:
Class类对象的getName()来产生全限定的类名,getSimpleName()和getCanonicalName()来产生不含包名的类名和全限定的类名。getName()和getCanonicalName()(1.5引入)方法的区别,前者返回的是虚拟机里面的class表示,后者返回的是更容易理解的表示,好比:
byte[]类型,前者[B,后者是byte[];
byte[][]类型,前者是[[B,后者是byte[][]。
isInterface()方法判断这个Class对象是不是一个借口。getInterfaces()方法返回的是该对象实现的接口。getSuperclass()查询Class对象的基类。newInstance()方法获得该类的一个新对象,使用它来建立对象必须带有默认的构造函数。
练习10让咱们编译一个程序,使它能过判断char数组是基本类型仍是一个对象。
char c = new char[3]; System.out.println(c.getClass().getCanonicalName()); System.out.println(c.getClass().getSuperclass().getCanonicalName());
输出显示c的基类是Object,说明他是一个对象。
从1.5开始为Class添加了泛型化处理,若是事先不知道Class的具体类型,能够写Class,也能够写Class表示它就是一个Number的子类,具体类型暂时不详。
向Class引用添加泛型语法的缘由是为了提供编译期类型检查。
对类FancyToy的Class对象的getSuperClass()方法仅仅返回的是Class的对象,? super FancyToy表示某个类,它是FancyToy超类。
在1.5中,Class添加了cast()方法,该方法接受对象,并将其转型为Class引用的类型。
14.3
P325代码后面:“抽象的getTypes()方法在导出类中实现”和本页最后一段“getTypes()方法一般只返回对一个静态List的引用”,方法名getTypes()错了应为types()。
Class的newInstance()方法须要处理两个异常:
Class
表示一个抽象类、接口、数组类、基本类型或 void; 或者该类没有空构造方法; 或者因为其余某种缘由致使实例化失败。instanceof有比较严格的限制,只能将对象与类的名字进行比较,而不能与Class对象作比较。
做者认为若是程序中编写了许多的instanceof表达式,说明程序的设计存在瑕疵。
Class对象的isInstance()方法与instanceof表达式的做用相同。
做者在本节最后的例子中使用了Class对象的isAssignableFrom(Class)方法,断定此 Class
对象所表示的类或接口与指定的 Class
参数所表示的类或接口是否相同,或是不是其超类或超接口。
14.5
用instanceof和Class.isInstance方法能够完美的判断类的继承的状况,而用==或者equals()比较两个Class对象,则不能正确的判断继承的状况。例如:
class Child extends Parent { } Child c = new Child(); System.out.println(c.getClass == Child.class); System.out.ptintln(c.getClass == Parent.class); System.out.println(c instanceof Child); System.out.println(c instanceof Parent);
输出的结果为
true
false
true
true
14.6
Class类与java.lang.reflect包中的类一块儿对反射进行了支持,该库包含了Field、Method以及Constructor类(每一个类都实现了Member接口)。
注意getConstructor()和getConstructors()方法只能获取public的构造函数。
14.7
在任什么时候刻,只要想要将额外的操做从“实际”对象中分离到不一样的地方,特别是当洗完容易作出修改,从没有使用额外操做转为使用这些操做,或者反过来时,代理显得特别有用。
Java的动态代理,能够动态的建立代理并动态的处理对所代理方法的调用。在动态代理商所作的全部调用都会被重定向到单一的调用处理器上。java.lang.reflect.Proxy的静态方法newProxyInstance()能够动态建立代理,这个方法须要获得一个类加载器(一般能够从已经被加载的对象中获取类加载器,而后传递给它),一个但愿代理实现的接口列表(不是类或者抽象类),以及一个InvocationHandler接口的一个实现(即调用处理器)。动态代理能够将全部调用重定向到调用处理器,所以一般会向调用处理器的构造函数传递一个实际对象的引用,从而使得调用处理器在执行其中介任务时能够将请求转发。
InvocationHandler的invoke()方法中第一个参数时代理对象,用来须要时区分请求的资源,可是在许多状况下并不须要它。
练习21和练习22翻译有误,原文写的是so that it measures method-call times,第一眼我也以为应该翻译成调用的次数,可是看答案的代码应该是调用的时间。
练习中让咱们System.out.println(proxy),致使了异常,看起来应该是循环调用了,以前书中有一句话:“在invoke()内部,在代理上调用方法时须要格外小心,由于对接口的调用将被重定向为对代理的调用”。此错误应该跟这句话有关,但我并非很理解。
14.8
空对象能够接受传递给它的表明对象的信息,可是将返回表示为实际上并不存在任何“真是”对象的值。经过这种方法,能够假设全部的对象都是有效的,而没必要浪费编程精力去检查Null。
为了建立空对象,最简单的方式使建立一个标记接口:
public interface Null { }
这使得instanceof能够探测空对象,而并不要求在全部的类中都添加isNull()方法。
一般能够将空对象设置为单例,这样就可使用equals()方法和==来判断是不是空对象。若是使用接口取代具体的空类,还可使用动态代理机制来自动建立空对象。
14.9
做者经过几个例子向咱们展现了Java的反射机制破坏了封装特性,经过反射你能够接触到普通类、包访问权限的类、私有内部类、匿名内部类的任何访问权限的域和方法,可是有一个特殊的地方,final域在遭遇反射修改时是安全的,运行时系统会在不抛出异常的状况下接受任何修改尝试,可是实际上不会发生任何修改。
14.10
做者在总结中提到他的一个编程思想:不要太早的关注程序的效率问题,最好首先让程序运做起来,而后再考虑它的效率。
若是要解决解决效率问题可使用profiler工具。