第十七章 反射与类加载器
17.1 运用反射
反射:.class文档反映了类基本信息,从Class等API取得类信息的方式称为反射。
17.1.1 Class与.class文档
1、java.lang.Class的实例代表Java应用程序运行时加载的.class文档,类、接口、Enum等编译过后,都会生成.class文档。Class类没有公开构造函数,实例时候JVM自动产生,每个.class文档加载时,JVM会自动生成对应的Class对象。
2、取得Class对象的方式:
(1)通过Object的getClass方法
(2)通过.class常量取得每个对象对应的Class对象
(3)如果是基本类型,可以使用对应的打包类加上.TYPE取得Class对象
3、默认首次加载.class文档时会执行静态区块。
4、类信息是在编译时期存储在.class文档,这是java支持执行运行时类型识别的方式。编译时期若使用到相关类,编译程序会检查对应的.class文档中记载的信息,以确定是否可完成编译。执行时期使用某类是,会先检查是否有对应的Class对象,如果没有,会加载对应的.class文档并生成对应的Class实例。
5、默认使用getClass()或.class取得的Class实例会是同一个对象。
17.1.2 使用Class.forName()
1、Class.forName()方法实现动态加载类,可用字符串指定类名称来获得类相关信息。
2、Class.forName()另一个版本可以让指定类名称、加载类时是否执行静态区块与类加载器。
static Class forName(String name,boolean initialize,ClassLoader loader)
17.1.3 从Class获得信息
Class对象代表加载的.class文档,取得Class对象后,就可以取得.class文档中加载的信息,像是包、构造函数、方法成员、数据成员等类型。
17.1.4 从Class建立对象
1、事先不知道类名称时,可以利用Class.forName()动态加载.class文档,取得Class对象之后,利用其newInstance()方法建立类实例。
Class clz = Class.forName(args[0]); Object obj = clz.newInstance();
2、若类定义有多个构造函数,也可以指定使用哪个构造函数生成对象,这必须在调用Class的getConstructor()方法时指定参数类型,取得代表构造函数的Constructor对象,利用Constructor的newInstance()指定创建时的参数值来建立对象。
3、生成数组:数组的Class实例是由JVM生成,也可以通过.class或getClass()取得Class实例。生成动态生成长度为10的java.util.ArrayList数组:
Class clz = java.util.ArrayList.class; Object obj = Array.newInstance(clz,10);
4、从第一个索引取得被手机对象实际的Class实例,此时就可以用它配合Array.newInstance()建立数组实例。
17.1.5 操作对象方法与成员
1、可以使用invoke()方法来动态调用指定的方法。
2、调用受把保护或私有方法,可以使用Class的getDeclaredMethod()取得方法,并在调用Method的setAccessible()时指定为true。
17.1.6 动态代理
1、在反射API中有个Proxy类,可动态建立接口的操作对象。
2、使用代理机制有两种代理方式:静态代理和动态代理。
3、静态代理
在静态代理实现中,代理对象与被代理对象必须实现同一接口,在代理对象中可以实现日志服务,必要时调用被代理对象,,可以这样使用代理对象:
Hello proxy = new HelloProxy(new HelloSpeaker()); proxy.hello("Justin");
创建代理对象HelloProxy时必须指定被代理对象HelloSpeaker,代理对象代理HelloSpeaker执行hello方法。
静态代理必须为个别接口操作出个别代理类,在应用程序行为复杂时,多个接口必须定义多个代理对象,操作与维护代理对象会有不少的负担。
4、动态代理
使用动态代理机制,可使用一个处理者代理多个接口的操作对象,处理者类必须操作java.lang.reflect.InvocationHandler接口。
使用Proxy.newPrxyInstance()方法建立代理对象,调用时必须指定类加载器,告知要代理的接口,以及接口上定义方法被调用时的处理者,Proxy.newPrxyInstance()方法底层会使用原生方式生成代理对象的Class实例,并利用它来生成代理对象。
17.2 了解类加载器
类加载器实际的职责就载入.class文档,JDK本身有默认的类加载器。
17.2.1 类加载器层级架构
1、Bootstrap Loader:产生Exrtended Loader和System Loader
Exrtended Loader:父加载器为Bootstrap Loader
System Loader:父加载器为Exrtended Loader
2、Bootstrap Loader通常由C撰写而成。若是Oracle的JDK,Bootstrap Loader会搜索系统参数sun.boot.class.path中指定位置的类,默认是JRE目录的classes中的.class文档,或lib目录中.jar文档里的类。
3、Exrtended Loader由Java撰写而成,会搜索系统参数java.ext.dirs中指定位置的类,默认是JRE目录lib\ext\classes中的.class文档。
4、System Loader由Java撰写而成,会搜索系统参数java.class.path中指定位置的类,也就是CLASSPATH路径,默认是当前工作路径下的.class文档。
5、加载类是会以Bootstrap Loader->Exrtended Loader->System Loader的顺序寻找类,如果所有类加载器都找不到指定类,就会抛出java.lang.NoClassDefFoundError.
6、对null调用getParent()方法会抛出NullPointedException异常。
7、ClassLoader可以使用loadClass()方法加载类,使用localClass方法加载类时,默认不会执行静态区块,真正使用类建立实例时才会执行静态区块。
17.2.2 建立ClassLoader实例
1、使用URLClassLoader来产生新的类加载器,需要java.net.URL作为其参数来指定类加载的搜索路径。使用URLClassLoader的loadClass()方法加载指定类时,会先委托父加载器代为搜索。
2、由同一类加载器载入的.class文档,只会有一个Class实例。如果同一.class文档由两个不同的类加载器载入,则会有两份不同的Class实例。
3、path可以输入不在System Loader以上层级类加载器搜索路径的其他路径。