<Java>21 反射
本文最后更新于:2023年3月30日 下午
21 反射
- 反射机制(Reflection)允许程序在执行期借助于 Reflection API 取得任何类的内部信息(如成员变量、成员方法等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到。
- 加载完类之后,在堆中就产生了一个
Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构。所以,形象地称之为:反射ocp 原则(开闭原则):不修改源码来扩展功能
计算机的三个阶段
-
代码阶段 / 编译阶段
编写代码 ——(Javac 编译)——> .class 字节码文件
-
Class 类阶段 / 加载阶段
字节码文件 ——(ClassLoader 类加载器)——>
Class类对象(堆中)· 字节码二进制数据 / 元数据(方法区)Class类对象包含:成员变量Field[] fields、构造器Constructor[] cons、成员方法Methord[] ms -
Runtime 运行阶段
创建对象,该对象知道其属于哪个
Class对象
反射机制可以完成
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时得到任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的成员变量和方法
- 生成动态代理
21.1 反射相关的常用类
-
java.lang.Class:代表一个类。Class对象表示某个类加载后在堆中的对象Class cls = Class.forName(classFullPath); //[1] Object o = cls.newInstance(); //[2]- 通过完整类名得到一个类的 Class 对象
- 通过该 Class 对象创建一个该类的 对象实例
-
java.lang.reflect.Method:代表类的方法。Method对象表示某个类的某个方法Method method = cls.getMethod(methodName); //[1] method.invoke(o); //[2]- 通过该 Class 对象得到一个 方法对象
- 方法对象.invoke:调用该方法
-
java.lang.reflect.Field:代表类的成员变量Field field = cls.getField(fieldName); //[1]- 该方法只能得到非私有对象
-
java.lang.reflect.Constructor:代表类的构造方法Constructor constructor = cls.getConstructor(); //[1] Constructor constructor2 = cls.getConstructor(String.class) //[2]- 得到一个无参构造器
- 得到一个形参是
(String str)的构造器
反射的优点和缺点
- 优点:可以动态地创建和使用对象(也是框架底层核心),使用灵活。没有反射机制,框架技术就失去底层支撑
- 缺点:使用反射基本是解释执行。这对执行速度有影响。
反射调用优化 - 关闭访问检查
-
Method和Field、Constructor对象都有setAccessible()方法 -
setAccessible()作用是启动和禁用访问安全检查的开关 -
参数值为 true,表示反射对象在使用时取消访问检查,这样能提高反射效率。
为 false 表示执行访问检查
21.2 Class 类
Class也是类,因此也继承Object类Class类不是 new 出来的,而是系统创建的- 对于某个类的
Class类对象,在内存中只有一份,因为类只加载一次 - 每个类的实例都会记得自己是由哪个
Class实例生成 - 通过
Class可以完整地得到一个类的完整结构,通过一系列 API Class对象是存放在堆的- 类的字节码二进制数据,是放在方法区的。有的地方称为类的元数据(包括 方法代码、变量名、方法名、访问权限 等)
21.2.1 Class 类的常用方法
-
Class.forName(String):返回指定类名的Class对象 -
newInstance():返回一个无参构造器创建的实例 -
getName():返回该Class对象表示的实体的全类名 -
getClass():返回该Class对象的运行类型java.lang.Class -
getPackage():返回该Class对象所在的包 -
getSuperClass():返回该Class对象的父类Class对象 -
getInterface():返回该Class对象的接口(数组) -
getAnnotations():返回注解信息(Annotation[]) -
getClassLoader():返回该Class对象的加载器(ClassLoader类型) -
getSuperclass():返回该Class对象实体的超类的Class -
getConstructors():返回本类所有包含public修饰的构造器的Constructor对象数组该方法返回的构造器不含父类构造器!
-
getDeclaredConstructer():返回本类所有构造器的Constructor对象数组 -
getFileds():返回一个包含public修饰的属性的Field对象的数组getFiled(String name):返回指定的Field -
getDeclaredFields():获取本类中所有属性 -
field.get(instance):返回指定实例的指定属性 -
field.set(instance, ..):给指定实例的指定属性赋值 -
getMethod():获得所有public修饰的方法的Method对象 -
getMethod(String name, Class paramTypes, ...):返回一个Method对象,其形参类型为 paramType -
getDeclaredMethod():获取本类中所有方法
21.2.2 获取 Class 对象
package com.melody.note;
class Test {}-
(编译阶段)已知一个类的全类名,且该类在类路径下:
Class cls1 = Class.forName("com.melody.note.Test");应用场景:配置文件,读取类全路径,加载类。
可能抛出
ClassNotFoundExcption -
(加载阶段)已知具体的类:
Class cls2 = Test.class;应用场景:参数传递。
该方法最为安全
-
(运行阶段)已知某个类的实例:
Class cls3 = new Test().getClass();应用场景:通过创建好的对象获取
Class对象 -
通过类加载器:
ClassLoader cll = new Test().getClass().getClassLoader(); Class cls4 = cll.loadClass("com.melody.note.Test"); -
基本数据类型:
Class clsB1 = int.class; Class<Boolean> clsB2 = boolean.class; -
基本数据类型包装类:
Class clsB3 = Character.TYPE; Class<Long> clsB4 = Long.TYPE;
21.2.3 哪些类有 Class 对象
- 外部类、成员内部类、静态内部类、局部内部类、匿名内部类
- 接口(interface)
- 数组
- 枚举(enum)
- 注解
- 基本数据类型
- void
21.3 类的加载
基本说明
反射机制是 Java 实现动态语言的关键,也就是通过反射实现类动态加载
- 静态加载:编译时加载相关的类,如果没有则报错。依赖性强
- 静态加载:运行时加载需要的类,如果运行时不用该类,则不报错。降低了依赖性
类加载时机
- 创建对象时(new) [静态加载]
- 子类被加载时,父类也被加载 [静态加载]
- 调用类中的静态成员 [静态加载]
- 通过反射 [动态加载]
(类加载图_21.3)
-
加载(Loading):
将类的 .class 文件读入内存,并为之创建一个 java.lang.Class 对象。此过程由类加载器完成
-
连接(Linking):
将类的二进制数据合并进 JRE 中
-
初始化(initialization):
JVM 负责对类进行初始化。这里主要是静态成员
21.3.1 类加载的五个阶段
-
加载阶段
JVM 在该阶段的主要目的是将字节码从不同数据源(.class 文件、jar 包、网络等)转化为二进制字节流加载到内存中,并生成一个代表该类的
java.lang.Class对象 -
连接阶段 - 验证
目的是确保
Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。包括:文件格式验证(是否以魔数 0xcafebabe 开头)、元数据验证、字节码验证、符号引用验证
可以考虑使用
-Xverify:none参数来关闭大部分的类验证措施,以缩短虚拟机加载的时间 -
连接阶段 - 准备
JVM 会在该阶段对 静态变量 分配内存并执行默认初始化。这些变量使用的内存都将在方法区中进行分配
public int n1 = 1; //实例属性,非静态变量,此阶段不分配内存 public static int n2 = 2; //静态变量,默认初始化为 0 public static final int n3 = 3; //static final 常量,静态初始化为 3 -
连接阶段 - 解析
JVM 将常量池内符号引用替换为直接引用的过程
-
初始化
到初始化阶段,才真正开始执行类中定义的 Java 程序代码。此阶段是执行
<clinit>()方法的过程<clinit>()方法是由编译器按语句在文件中出现的顺序,依次自动收集类中所有静态变量的赋值动作和静态代码块中的语句,并进行合并JVM 会保证一个类的
<clinit>()方法在多线程环境中被正确地加锁、同步。如果多个线程去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕
21.4 通过反射获取类的结构信息
java.lang.Class 类(与前面的的重复)
getSuperClass():返回该Class对象的父类Class对象
getInterface():返回该Class对象的接口(数组)
getAnnotations():返回注解信息(Annotation[])
getClassLoader():返回该Class对象的加载器(ClassLoader类型)
getSuperclass():返回该Class对象实体的超类的Class
getConstructors():返回本类所有包含public修饰的构造器的Constructor对象数组该方法返回的构造器不含父类构造器!
getDeclaredConstructer():返回本类所有构造器的Constructor对象数组
getFileds():返回一个包含public修饰的属性的Field对象的数组
getFiled(String name):返回指定的Field
getDeclaredFields():获取本类中所有属性
field.get(instance):返回指定实例的指定属性
field.set(instance, ..):给指定实例的指定属性赋值
getMethod():获得所有public修饰的方法的Method对象
getMethod(String name, Class paramTypes, ...):返回一个Method对象,其形参类型为 paramType
getDeclaredMethod():获取本类中所有方法
java.lang.reflect.Field 类
getModifiers():以 int 形式返回修饰符默认修饰符 [0]、public [1]、private [2]、protected [4]、static [8]、final [16]
示例:
public static final int n = 0;这个变量的修饰符的 int 表示 = 1 + 8 + 16 = 25
getType():以Class形式返回类型上例变量的
getType()等同于Integer.getClass()
getName():返回属性名
java.lang.reflect.Method 类
getModifiers():以 int 形式返回修饰符(同上)getName():返回方法名getReturnType():以Class形式返回返回类型getParameterTypes():以Class[]形式返回形参类型数组
java.lang.reflect.Constructer 类
getModifiers():以 int 形式返回修饰符getName():返回构造器名(和全类名相等)getParameterTypes():以Class[]形式返回形参类型数组
21.5 通过反射创建对象
-
调用类中的 public 修饰的无参构造器
Object obj1 = cls.newInstance(); -
调用类中指定的构造器
Constructer cons = cls.getConstructer(int.class, String.class, ...); Object obj2 = cons.newInstance(1, "nnn", ...); -
setAccessible(true):爆破(暴力破解)。使用反射可以访问 private 构造器Constructer cons2 = cls.getDeclaredConstructer(boolean.class ...); cons2.setAccessible(true); Object obj3 = cons.newInstance(false, ...);
21.6 通过反射访问成员
Field field = cla.getDeclaredField("name"); field.setAccessible(true); field.set(o, "111"); //[1]
o 表示一个类的实例
如果该属性是静态属性,则对象 o 可以是 null
Method method = cls.getDeclaredMethod("m1"); method.setAccessible(true); Object returnObj = method.invoke(o, 'c', ...); //[1]
o 表示一个类的实例,后面是实参列表
同理,静态方法的场合,对象 o 可以是 null
