反射
★★★第二十章 反射
★★★反射机制
反射机制允许程序在执行期间借助于
Reflection API取得任何类的内部信息(比如成员变量,构造器,成员方法等等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到。加载完类之后,在堆中就产生了一个
Class类型的对象(**Class也是一个类,类名就叫Class:class Class。一个类只有一个Class对象),这个对象包含了类的完整结构信息,通过这个对象可以得到类的结构。这个Class对象就像一面镜子,透过这个镜子可以看到类的结构,所以,形象的称之为:反射**。Java反射机制可以完成:- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时得到任意一个类所具有的成员变量和方法。
- 在运行时调用任意一个对象的成员变量和方法。
- 生成动态代理。
★★★Java反射机制原理图
Java程序在计算机中有三个阶段:代码阶段/编译阶段:
Java程序源代码Cat.java经过javac编译成.class字节码文件Cat.class。
**
Class类阶段(加载阶段)**:- 当要创建一个
Cat对象cat时,类加载器ClassLoader会将Cat类的Class对象加载到堆中,该对象拥有Cat类的结构。在得到Class对象后,就可以在堆中创建这个cat对象了。
- 当要创建一个
Runtime运行阶段:- 被创建的
cat对象知道自己属于哪一个Class对象,然后cat对象就可以进行接下来的操作了,例如调用Cat类的成员方法:cat.hi()。
- 被创建的

★反射相关类
java.lang.Class:代表一个类,**Class对象表示某个类加载后在堆中的对象**。java.lang.reflect.Method:代表类的方法,**Method对象表示某个类的方法**。java.lang.reflect.Field:代表类的成员变量,**Field对象表示某个类的成员变量**。java.lang.reflect.Constructor:代表类的构造方法,**Constructor对象表示某个类的构造器**。
主程序:
Reflection01.java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41package com.f.chapter20.reflection;
import java.io.FileInputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Properties;
/**
* @author fzy
* @date 2023/7/24 16:58
* 反射相关类的使用
*/
public class Reflection01 {
public static void main(String[] args) throws Exception {
//1.使用 Properties 类, 从配置文件中读取类的相关信息
Properties properties = new Properties();
properties.load(new FileInputStream("C:\\Users\\1\\Code Project\\Java project\\file\\cat.properties"));
String classPath = properties.getProperty("class");
String method = properties.getProperty("method");
String field = properties.getProperty("field");
//2.使用Java反射机制
// 2.1 java.lang.Class的使用
Class catClass = Class.forName(classPath); //根据配置文件得到Cat类的Class对象
Object o = catClass.newInstance(); //根据该Class对象创建Cat类的对象
// 2.2 java.lang.reflect.Method的使用
Method catMethod = catClass.getMethod(method); //根据配置文件得到Cat类的相应方法
//传统方法: 对象.方法(); 反射机制: 方法.invoke(对象)
catMethod.invoke(o); //通过invoke函数调用该对象的该相应方法
// 2.3 java.lang.reflect.Field的使用
//gatField不能得到类的私有成员变量
Field nameField = catClass.getField(field); //根据配置文件得到Cat类的相应字段
//传统方法: 对象.成员变量; 反射机制: 成员变量.get(对象)
System.out.println(nameField.get(o)); //通过get函数得到该对象的该相应成员变量
// 2.4 java.lang.reflect.Constructor的使用
Constructor constructor1 = catClass.getConstructor(); //()中可以指定构造器的参数类型
System.out.println(constructor1); //public com.f.chapter20.Cat()
Constructor constructor2 = catClass.getConstructor(String.class); //这里传入的是String类的Class对象
System.out.println(constructor2); //public com.f.chapter20.Cat(java.lang.String)
}
}配置文件:
cat.properties1
2
3class=com.f.chapter20.Cat
method=showAge
field=age相关的类:
Cat.java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25package com.f.chapter20;
/**
* @author fzy
* @date 2023/7/24 17:01
*/
public class Cat {
private String name = "Milk";
public int age = 10;
public Cat() {
}
public Cat(String name) {
this.name = name;
}
public void hi() {
System.out.println(name + " say hi!");
}
public void showAge() {
System.out.println(name + " is " + age + " years old.");
}
}
反射调用优化
反射优点和缺点:
- 优点:可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑。
- 缺点:使用反射基本是解释执行,对执行速度有影响。
反射调用优化 - 关闭访问检查
Method和Field、Constructor对象都有setAccessible()方法。setAccessible作用是启动和禁用访问安全检查的开关。- 参数值为
true表示反射的对象在使用时取消访问检查,提高反射的效率。参数值为false则表示对反射的对象执行访问检查。
★★★Class类
public final class Class<T> implements java.io.Serializable, GenericDeclaration, Type, AnnotatedElement
Class也是类,因此也继承Object类。Class类对象不是new出来的,而是系统创建的。- 对于某个类的
Class类对象,在内存中只有一份,因为类只加载一次。 - 每个类的实例都会记得自己是由哪个
Class实例所生成的。 - 通过
Class类对象可以完整地得到一个类的完整结构,通过一系列API调用即可。 Class对象是存放在堆的。- 类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括方法代码,变量名,方法名,访问权限等等)。
★Class类的常用方法
- 最根本的是要先得到对应类的
Class类对象。
Field[] getFields():获取所有public修饰的属性,包括本类及其父类的。Field[] getDeclaredFields():获取该类中所有的属性。Method[] getMethods:获取所有public修饰的方法,包括本类及其父类的。Method[] getDeclaredMethods:获取该类中所有的方法。Constructor<?>[] getConstructors():获取所有public修饰的构造器,只包括本类的。Constructor<?>[] getDeclaredConstructors():获取该类中所有的构造器。
1 | package com.f.chapter20.class_; |
1 | package com.f.chapter20; |
★★★获取Class对象的六种方法
对应于
Java程序在计算机中的三个阶段,有不同的获取Class对象的方法:
前提:已知一个类的全类名,且该类在类路径下,则可通过
Class类的静态方法forName()获取,可能抛出ClassNotFoundException。例如:Class carClass1 = Class.forName(classPath);。应用场景:多用于配置文件,读取类的全路径,加载类。
前提:若已知具体的类,通过类的
class获取,该方式最为安全可靠,程序性能最高。例如:Class carClass2 = Car.class;。应用场景:多用于参数传递,比如通过反射得到对应构造器对象。例如下面的代码:
Constructor constructor2 = catClass.getConstructor(String.class);中的String.class。前提:已知某个类的实例,调用该实例的
getClass()方法获取Class对象。例如:Class carClass3 = car.getClass();。
应用场景:通过创建好的对象,获取 Class 对象。通过类加载器
ClassLoader来获取到类的Class对象。例如:Class carClass4 = classLoader.loadClass(classPath);。基本数据类型按如下方式得到
Class类对象:Class cls1 = 基本数据类型.class;。基本数据类型对应的包装类,可以通过
.TYPE得到对应的Class类对象:Class cls2 = 包装类.TYPE;。- 注意:基本数据类型及其对应的包装类,它们的
Class类对象是同一个。
- 注意:基本数据类型及其对应的包装类,它们的
1 | package com.f.chapter20.class_; |
拥有Class类对象的类型
- 如下类型有
Class类对象:- 外部类,成员内部类,静态内部类,局部内部类,匿名内部类。
interface:接口。- 数组。
enum:枚举。annotation:注解。- 基本数据类型。
void。
★★★类加载
静态加载和动态加载
- 静态加载:编译时加载相关的类,如果没有该类则报错,依赖性太强。
- 动态加载:运行时加载需要的类,如果运行时不使用该类,则即使该类不存在,也不报错,降低了依赖性。
★类加载时机
- 当创建对象时 (
new)。-> 静态加载 - 当子类被加载时,父类也加载。 -> 静态加载
- 调用类中的静态成员时。 -> 静态加载
- 通过反射。-> 动态加载
★★★类加载流程图
- 类加载对应于 “★★★Java反射机制原理图” 小节的第二个阶段。

- 类加载三个阶段:
- **加载
Loading**:将类的.class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成。 - **连接
Linking**:将类的二进制数据合并到JRE中。- 验证
verification:对文件的安全进行校验,如文件描述符、元数据、字节码文件安全性等。 - 准备
preparation:对静态变量进行默认初始化并分配内存空间。 - 解析
resolution:把符号引用转换为直接引用。
- 验证
- **初始化
initialization**:JVM负责对类进行初始化,这里主要是指静态成员。
- **加载
加载阶段
JVM在该阶段的主要目的是将字节码从不同的数据源(可能是class文件、也可能是jar包,甚至网络)转化为二进制字节流加载到内存(方法区)中,并生成一个代表该类的java.lang.Class对象。
连接阶段 - 验证
- 目的是为了确保
Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。 - 包括:文件格式验证(是否以魔数
oxcafebabe开头)、元数据验证、字节码验证和符号引用验证。 - 可以考虑使用
-Xverify:none参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间。
连接阶段 - 准备
JVM会在该阶段对静态变量,分配内存并初始化(对应数据类型的默认初始值如0、0L、null、false等)。这些变量所使用的内存都将在方法区中进行分配。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package com.f.chapter20.classload;
/**
* @author fzy
* @date 2023/7/25 15:17
* 类在加载阶段中,连接阶段-准备 的分析
*/
public class ClassLoad_ {
}
class A {
//n1 是实例属性,不是静态属性,因此在准备阶段是不会分配内存的
public int n1 = 10;
//n2 是静态变量,在"连接阶段-准备"时,在方法区中分配内存,n2 默认初始化为 0,在初始化阶段才会赋值为 20
public static int n2 = 20;
//N3 是静态常量,因为它一旦赋值就不变了,所以在"连接阶段-准备"时,N3 就直接赋值为 30
public static final int N3 = 30;
}
连接阶段 - 解析
虚拟机将常量池内的符号引用替换为直接引用的过程。
在
Class类对象还没有创建(分配内存)时,通过符号来引用;在Class类对象创建(分配内存)后,通过内存地址来引用,即直接引用。
初始化阶段
- 到初始化阶段,才真正开始执行类中定义的
Java程序代码,此阶段是执行<clinit>()方法的过程。 <clinit>()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。- 虚拟机会保证一个类的
<clinit>()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。
★获取类结构信息
- 在 “★Class类的常用方法” 小节中,含有一系列获取类结构信息的方法,如获取类的
Field、Method、Constructor对象。接下来的内容是在获取了类的Field、Method、Constructor对象后,进一步获得这些对象的相关信息。下面代码例子中的Car类,就是在 “★Class类的常用方法” 小节中的Car类。
java.lang.reflect.Field
getModifiers:以int形式返回修饰符(默认修饰符为0、public为1、private为2、protected为4、static为8、final为16)。如果是
public static则返回1+8=9,以此类推。getType:返回属性所对应的类的Class对象。getName:返回属性名。
1 | package com.f.chapter20.class_; |
java.lang.reflect.Method
getModifiers:以int形式返回修饰符(默认修饰符为0、public为1、private为2、protected为4、static为8、final为16)。如果是
public static则返回1+8=9,以此类推。getReturnType:返回方法所对应的类的Class对象。getName:返回方法名。getParameterTypes:以Class[]形式返回方法的形参的类型的Class对象数组。
1 | package com.f.chapter20.class_; |
java.lang.reflect.Constructor
getModifiers:以int形式返回修饰符(默认修饰符为0、public为1、private为2、protected为4、static为8、final为16)。如果是
public static则返回1+8=9,以此类推。getName:返回构造器名(全类名)。getParameterTypes:以Class[]形式返回构造器的形参的类型的Class对象数组。
1 | package com.f.chapter20.class_; |
★通过反射创建对象
- 方式一:调用类中的
public修饰的无参构造器。 - 方式二:调用类中的指定构造器。
- Class 类相关方法:
newInstance:调用类中的无参构造器,获取对应类的对象。getConstructor(Class.clazz):根据参数列表,获取对应的构造器对象(需要为public)getDecalaredConstructor(Class.clazz):根据参数列表,获取对应的构造器对象
Constructor类相关方法:setAccessible:暴破newlnstance(Object..obj):调用构造器
1 | package com.f.chapter20.reflection; |
暴破
- 通过上面的例子可以看到,使用反射可以访问类的私有的构造器/方法/属性,相当于暴力破解(暴破名称的由来)了类的私有、外部不可访问机制。
★通过反射访问类中的属性
- 根据属性名获取
Field对象:Field f = clazz对象.getDeclaredField(属性名);。 - 暴破:
f.setAccessible(true); //f是Field - 访问:
f.set(o, 值);System.out.println(f.get(o));
- 注意:如果是静态属性,则
set和get中的参数o,可以写成null。
1 | package com.f.chapter20.reflection; |
★通过反射访问类中的方法
- 根据方法名和参数列表获取
Method方法对象:Method m = clazz.getDeclaredMethod(方法名,XX.class);。 - 获取对象:
Object o = clazz.newlnstance();。 - 暴破:
m.setAccessible(true);。 - 访问:
Object returnValue = m.invoke(o, 实参列表);。 - 注意:如果是静态方法,则
invoke的参数o,可以写成null。
1 | package com.f.chapter20.reflection; |
★★★反射相关方法小结
- 在 “★Class类的常用方法” 小节中的方法,主要是用于获得类的
Field、Method、Constructor对象。 - 在 “★获取类结构信息” 小节中的方法,主要是用于对得到的类的
Field、Method、Constructor对象,可以进一步得到这些对象的相关信息。 - 在 “★通过反射创建对象”、“★通过反射访问类中的属性”、“★通过反射访问类中的方法” 小节中的方法,主要是用于根据得到的类的
Field、Method、Constructor对象,能对类的实例进行操作。