Java - 反射
一、反射的概念
Java程序中,所有的对象都有两种类型:编译时类型和运行时类型,而很多时候对象的编译时类型和运行时类型不一致,为了解决这些问题,程序需要在运行时发现对象和类的真实信息,现在有两种方案
- 方案1:在编译和运行时都完全知道类型的具体信息,在这种情况下,我们可以直接先使用instanceof运算符进行判断,再利用强制类型转换符将其转换成运行时类型的变量即可
- 方案2:编译时根本无法预知该对象和类的真实信息,程序只能依靠运行时信息来发现该对象和类的真实信息,这就必须使用反射
类加载器加载完类之后,就会产生一个Class类型的对象,并引用存储到方法区,那么每一个类在方法区内存都可以找到唯一Class对象与之对应,这个对象包含了完整的类的结构信息,我们可以通过这个对象获取类的结构。这种机制就像一面镜子,Class对象像是类在镜子中的镜像,通过观察这个镜像就可以知道类的结构,所以,把这种机制形象地称为反射机制
反射:Class对象(镜像)–>类(原物)
二、类加载(了解)
- 类在内存中完整的生命周期:加载–>使用–>卸载
1、类的加载过程
类加载:当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过加载、连接、初始化三个步骤来对该类进行初始化,如果没有意外,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载
类的加载又分为三个阶段:
- 加载(load):指将类型的class字节码数据读入内存
- 连接(link)
- ①验证:校验合法性等
- ②准备:准备对应的内存(方法区),创建Class对象,为静态变量赋默认值,为静态常量赋初始值
- ③解析:把字节码中的符号引用替换为对应的直接地址引用
- 初始化(initialize):即执行类初始化方法,大多数情况下,类的加载就完成了类的初始化,有些情况下,会延迟类的初始化
2、类的初始化场景
以下情况如果类没有初始化,会先完成类初始化
- 运行主方法所在的类,所在类会初始化
- 子类初始化时,父类会先初始化
- 创建类对象
- 调用某个类的静态变量或静态方法
- 通过反射操作某个类
类初始化执行的是
以下情况如果类没有初始化,不会导致类初始化
- 使用某个类的静态常量
- 子类调用父类的静态变量或静态方法,子类不会初始化
- 用某个类型声明数组并创建数组对象
3、类加载器
想要更好的解决java.lang.ClassNotFoundException或java.lang.NoClassDefError这类问题,或者在一些特殊的应用场景,比如需要支持类的动态加载或需要对编译后的字节码文件进行加密解密操作,那么需要自定义类加载器
类加载器分为:
- 引导类加载器(Bootstrap Classloader)又称为根类加载器
- 负责加载jre/rt.jar核心库
- 本身不是Java代码实现的,也不是ClassLoader的子类,获取它的对象时往往返回null
- 扩展类加载器(Extension ClassLoader)
- 负责加载jre/lib/ext扩展库
- 是ClassLoader的子类
- 应用程序类加载器(Application Classloader)
- 负责加载项目的classpath路径下的类
- 是ClassLoader的子类
- 自定义类加载器(tomcat中有自定义类加载器)
- 当程序需要加载“特定”目录下的类
- 当程序的字节码文件需要加密时,会提供一个自定义类加载器对其进行解码
Java系统类加载器的双亲委托模式:下一级的类加载器,如果接到任务,会先搜索是否加载过,如果没有,会先把任务往上传,如果都没有加载过,一直到根加载器,如果根加载器在它负责的路径下没有找到,会往回传,如果一路回传到最后一级都没有找到,那么会报ClassNotFoundException或NoClassDefError,如果在某一级找到了,就直接返回Class对象(父子类加载器不是继承关系,以组合的方式实现的)
- 应用程序类加载器的父加载器:扩展类加载器
- 扩展类加载器的父加载器:引导类加载器
获取默认的系统类加载器:
ClassLoader.getSystemClassLoader()
- 查看类加载器:
Class对象.getClassLoader()
- 查看类加载器的父加载器:
ClassLoader对象.getParent()
- 查看类加载器:
三、java.lang.Class类
反射相关API
java.lang.Class
(Class对象是反射的根源)java.lang.reflect.*
获取Class对象的四种方式
类型名.class
:要求编译期间已知类型对象.getClass()
:获取对象的运行时类型Class.forName(类型全名称)
:可以获取编译期间未知的类型ClassLoader的类加载器对象.loadClass(类型全名称)
:可以用系统类加载对象或自定义加载器对象加载指定路径下的类型
四、反射的基本应用
1、获取类型的详细信息
- 可以获取:包、修饰符、类型名、父类、父接口、类成员(属性、构造器、方法)、注解
2、创建任意引用类型的对象
- 方式一:直接通过Class对象来实例化(要求必须有公共的无参构造)
- 获取该类型的Class对象
- 创建对象
- 方式二:通过获取构造器对象来进行实例化
- 获取该类型的Class对象
- 获取构造器对象
- 创建对象
如果构造器的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)
3、操作任意类型的属性
- 获取该类型的Class对象:
Class clazz = Class.forName("包.类名");
- 获取属性对象:
Field field = clazz.getDeclaredField("属性名");
- 如果属性的权限修饰符不是public,那么需要设置属性可访问:
field.setAccessible(true);
- 创建实例对象:如果操作的是非静态属性,需要创建实例对象:
- 有公共的无参构造:
Object obj = clazz.newInstance();
- 有参构造:
Object obj = 构造器对象.newInstance(实参...);
- 有公共的无参构造:
- 设置属性值:
field.set(obj,"属性值");
- 如果操作静态变量,那么实例对象可以省略,用null表示
- 获取属性值:
Object value = field.get(obj);
- 如果操作静态变量,那么实例对象可以省略,用null表示
4、调用任意类型的方法
- 获取该类型的Class对象:
Class clazz = Class.forName("包.类名");
- 获取方法对象:
Method method = clazz.getDeclaredMethod("方法名",方法的形参类型列表);
- 创建实例对象:
Object obj = clazz.newInstance();
- 调用方法:
Object result = method.invoke(obj, 方法的实参值列表);
- 如果方法的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)
- 静态方法,实例对象也可以省略,用null代替