重点介绍 java.lang.reflect 包下的接口和类。包括 Class 、Method、Feild、Constructor、Array 等,分别代表类、方法、成员变量、构造器和数组。Java 程序可以使用这些类动态的获取某个对象、某个类的运行时信息,并可以动态的创建 Java 对象,动态的调用 Java 方法,访问并修改制定对象的成员变量值。
类的加载、连接、初始化
1.同一个JVM的所有线程、变量都处于同一个进程中,并使用该JVM中的内存区。JVM 会终止的情况:
》 程序运行完正常结束;
》 程序运行到System.exit() 或 Runtime.getRuntime().exit();
》 程序执行过程中遇到未捕获的异常或错误而结束。
》程序所在平台强制结束JVM进程。
2.类加载/类初始化
当程序主动使用某个类时,这个类还未被加载到内存中,则系统会通过加载、连接、初始化三个步骤对这个类初始化,具体操作是将类的 class 文件读到内存,并为之创建一个 java.lang.Class 对象。
通过 JVM 提供的系统类加载器完成类的加载,我们可以通过继承 ClassLoader 基类创建自定义类加载器。
不会等到首次使用该类时加载,允许系统预先加载某些类,加载的是类的二进制数据,这些类的来源:
- 本地文件系统要加载的类
- JAR包文件中的类
- 通过网络加载的 class 文件
- Java源代码动态编译并加载
3.类的连接
类加载 -> 生成 Class 对象 -> 类连接 -> 把二进制数据合并到JRE
类连接的三个阶段:
验证阶段检验被加载的类是否有正确的内部结构 -> 准备阶段为类的类变量分配内存并设初始值 -> 解析阶段将类的二进制数据中的符号引用换为直接引用
4.类初始化
对类变量初始化,指定初始值的俩种方式:1、声明类变量时指定初始值;2、使用静态初始化块指定初始值
5.类初始化的时机
某个类创建实例的方式有三种:
- 使用 new 操作符创建实例
- 通过反射创建实例
- 通过反序列化的方式创建实例
何时系统初始化该类或接口
- 创建类实例
- 调用类的类方法(静态方法)
- 访问类或接口的类变量,或为其赋值
- 使用反射强制创建某个类的或接口的 java.lang.Class 对象
- 初始化某个类的子类,这时该类的父类都会被初始化
- 使用 java.exe 命令运行某个类
注意!
- 访问 final 类型的类变量,在编译时已经确定其值,只会调用该变量的值,被当作常量访问,不会初始化该类;但编译时值不能被确定下来如下:
static final String compilezConstant = System.currentzTimeMillis() + ""
则会导致该类初始化;
2. 使用 ClassLoader 类的 loadClass() 方法加载类时只会加载,不会执行类初始化。使用 Class 的 forName() 静态方法会导致强制初始化类
类加载器
负责将磁盘或网络的 .class 文件加载到 JVM 内存,并生成对应的 java.lang.Class 对象。
JVM 启动时,形成三个类加载器组成的初始化类层次结构:
- Bootstrap ClassLoader 根类加载器/引导类加载器,加载 Java 的核心类,如 String 、System
- Extension ClassLoader 扩展类加载器, JAVA/jre/lib/ext/ 路径下的类
- System ClassLoader 系统类加载器,-classpath 选项、java.class/path 系统属性或 CLASSPATH 环境变量指定的JAR包或类路径
- 用户类加载器,继承ClassLoader
类的加载机制
- 全盘负责,即不仅加载类本身,还加载其依赖的和引用的类
- 父类委托,先通过父类加载,不成功则从自己的途径加载
- 缓存机制,先从缓存区搜寻类,没有则读系统的二进制数据
创建并使用自定义的类加载器
通过扩展ClassLoader 的字类,重写其方法可以实现自定义的类加载器,ClassLoader 的俩个关键方法:
1. loadClass(String name, boolean resolve) : ClassLoader 的入口点,根据指定名称加载类,系统调用这个方法来获取指定类对应的Class 对象
2. findClass(String name):根据名称查找类
重写以上方法可以实现自定义的类加载器,推荐重写 findClass(String name) ,可以避免覆盖默认的父类委托 、缓冲机制,loadClass 逻辑复杂
defineClass()方法管理 JVM 的复杂实现,将字节码分析成运行时结构,并校验有效性
URLClassLoader 类
这个类是系统类加载器和扩展类加载器的父类,功能强大,既能从本地文件系统获取二进制文件来加载类,也能从远程主机获取二进制文件来加载类。重要构造器:
1. URLClassLoader(URL[] urls) 使用默认父类加载器创建 ClassLoader
2. URLClassLoader(URL[] urls,ClassLoader parent) 使用指定的
一旦得到URLClassLoader 对象,就可以调用该对象的 loadClass() 方法加载指定类
public static Connection conn;
public static Connection getConn(String url,String user,String pass) throws Exception{
if(conn == null ){
/*file: 从本地文件系统加载
* http: 从互联网通过HTTP访问加载
* fip: 从互联网通过FTP访问加载
* */
URL[] urls = {new URL("file:mysql-connection-java-5.1.3-bin.jar")};
/*以默认的ClassLoader作为父类加载器*/
URLClassLoader myURLClassLoader = new URLClassLoader(urls);
Driver driver = (Driver)myURLClassLoader.loadClass("").newInstance();
Properties properties = new Properties();
properties.setProperty("User",user);
properties.setProperty("Pass",pass);
conn = driver.connect(url,properties);
}
return conn;
}
通过反射查看类的信息
许多对象在运行时会出现俩种类型:编译时类型(存储在内存的)和运行时类型(访问到的数据)
Person p = new Student(); p变量的编译时类型Person;运行时类型 Student
由于这种需求,程序需要在运行时发现对象和类的真实信息,有俩种方法解决此问题:
1. 假设在编译时和运行时完全知道类型的具体信息,可以使用instanceof运算符进行判断,再利用强制类型转换为运行时类型的变量;
2. 在编译时无法预知该对象和类可能属于哪些类,只能通过运行时信息发现该对象和类的真实信息,需要用反射!
获得 Class 对象
类加载 -> Class 对象 -> 通过这个Class 对象可以访问 JVM 中的这个类,Java 程序中如何获取 Class 对象?
- 使用Class 类的 forName(String clazzName)静态方法,参数是类的完整包名+类名
- 调用某个类的 class 属性获取其对应的 Class 对象,如Person.class 返回它对应的Class 对象
- 调用某个对象的 getClass()
获得了这个 Class 对象,则可以通过它访问原来类的构造器、方法、变量、注释等
Java 8 新增的方法参数反射:Executable 抽象基类
这个对象代表可执行的类成员,派生出Constructor 、Method 俩个字类
用来获取方法或构造器参数的真正信息
使用反射生成并操作对象
Class 对象可以获取该类里的方法(由 Method 对象表示)、构造器(Constructor)、成员变量(Field),这三个类都在 java.lang.reflect 包下,且实现了 java.lang.reflect.Member 接口,所以程序可以操作其中的对象。
创建对象
通过反射生成对象的俩种方式:
1. 使用 Class 对象的 newInstance() 方法使用默认构造器创建 Class 对象对应类的实例,这个类要有默认构造器;
2. 使用 Class 对象获取的 Constructor (构造器)对象调用newInstance() 方法创建 Class 对象对应类的实例
调用方法
获取类 Class 对象,可以通过 getMethods() 或 getMethod() 方法获取全部方法或指定方法,然后可以通过 Methed 来调用其对应的方法。
Methed 中 invoke() 来执行调用
- Object invoke(Object obj, Object…args) obj 为主调,args 是传入的实参
访问成员变量值
通过 Class 对象的 getFields() 或 getField() 获取该类的全部成员变量或指定成员变量。Field 提供一下俩组方法读取或设置成员变量值
- getXxx(Object obj) 获取变量的值
- setXxx(Object obj,Xxx val) 设置变量的值
若是引用类型去掉后面的Xxx
通过setAccessible(boolean value) 可以设置通过反射访问该成员变量是否取消访问权限检查**
操作数组
Array 类,可以动态创建数组,操作数组。方法:
- static Object newInstance(Class<?> componentType,int…length)创建指定元素类型和维度的数组
- static xxx getXxx(Object array, int index)
- static void setXxx(Object array, int index,xxx val)
使用反射生成 JDK 动态代理
在 java.lang.reflect 包下提供了 Proxy 类和 InvocationHandler 接口,使用它们可以生成 JDK 动态代理类或动态代理对象
1. 使用 Proxy 类和 InvocationHandler 接口 创建动态代理
Proxy 的俩个创建动态代理类和动态实例的方法
- static Class<?> getProxyClass(ClassLoader loader, Class<?>… interfaces):创建一个动态代理类所对应的Class对象,实现interfaces所指定的多个接口。根据ClassLoader类加载器生成指定动态代理类。
- static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h):直接创建一个动态代理对象,该代理对象的实现类实现了interfaces指定的系列接口,执行代理对象的每个方法时都会被替换执行InvocationHandler对象的invoke方法。
使用步骤:
创建原始的类对象,根据该对象创建动态代理类对象,此时动态代理类对象可以访问原始对象的所有方法,且在自实现的 InvocationHandler 类的 invoke() 方法中可以插入想要复用的代码块。
此为 AOP (aspect Orient Programming,面向切面编程),AOP 代理包含了目标对象的全部方法。
反射和泛型
在反射中使用泛型可以避免使用发射生成的对象需要强制类型转换。
使用反射获取泛型信息
例如 获取 Map
普通变量数据类型获取方法
Class<?> a = field.getType();
泛型书记类型获取方法
Type gTypr = field.getGenericType();
然后将这个 Type 对象强制转换为 ParameterizedType (带参数的类型),增加了泛型类型的限制,ParameterizedType 获取类型方法:
- getRawType() 返回没有泛型信息的原始类型
- getActualTypeArguments 返回泛型参数的类型