<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