概念
Java 的反射(Reflection)是一种在运行时动态获取类的信息(如类名、属性、方法、构造函数等),并且可以在不知道类的编译时定义的情况下创建对象、调用方法、修改属性的机制。它让程序具备“自省能力”,可以在执行过程中查看和操作自身结构。
类比到 PHP,Java 的反射机制就有点像 PHP 的 ReflectionClass
、call_user_func
、call_user_func_array
这些工具——在 PHP 中你可以通过类名的字符串来实例化对象、调用某个方法,而不需要在编译时明确写死。
Java 的反射 API 提供了一系列的类和接口来操作 Class 对象。主要的类包括:
java.lang.Class
:表示类的对象。提供了方法来获取类的字段、方法、构造函数等。java.lang.reflect.Field
:表示类的字段(属性)。提供了访问和修改字段的能力。java.lang.reflect.Method
:表示类的方法。提供了调用方法的能力。java.lang.reflect.Constructor
:表示类的构造函数。提供了创建对象的能力。
一般地,Java 反射的工作流程就是:程序在运行时先获取类的 Class
对象,再通过它找到目标字段/方法/构造器,最后在对象上动态读写或调用
操作
获取对象
首先第一步我们就是获取类的 Class
对象,总的来说,有五种方法
- 通过类的字面量:
Class<?> clazz = String.class;
- 通过对象实例:
String str = "Hello"; Class<?> clazz = str.getClass();
- 通过
Class.forName()
方法:Class<?> clazz = Class.forName("java.lang.String");
- 通过类加载器:
Class<?> clazz = ClassLoader.getSystemClassLoader().loadClass("java.lang.String");
- 通过基本类型的 TYPE 字段:
Class<?> clazz = Integer.TYPE;
接下来使用五种方法来写demo,第一种适用于编译时已知类(比如String类)进行静态引用,直接利用.class
就可以获取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| package Base.Reflection;
class Reflect{
}
public class test {
public static void main(String[] args) {
Class<?> clazz2 = String.class;
System.out.println(clazz2.getName());
Class<?> clazz1 = Reflect.class;
System.out.println(clazz1.getName());
}
}
/*
java.lang.String
Base.Reflection.Reflect
|
第二种必须是已经有了实例才能够成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| package Base.Reflection;
class Reflect1{
}
public class test1 {
public static void main(String[] args) {
String str = "hello";
// String str = new String("Hello");
Class<?> clazz1 = str.getClass();
System.out.println(clazz1.getName());
Reflect1 Reflection = new Reflect1();
Class<?> clazz2 = Reflection.getClass();
System.out.println(clazz2.getName());
}
}
/*
java.lang.String
Base.Reflection.Reflect1
|
第三种是当我们知道类的名字是,直接显式的按字符串加载类,并触发类初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| package Base.Reflection;
class Reflect1{
}
public class test1 {
public static void main(String[] args){
Class<?> clazz1 = Class.forName("java.lang.String");
System.out.println(clazz1.getName());
Class<?> clazz2 = Class.forName("Base.Reflection.Reflect1");
System.out.println(clazz2.getName());
}
}
|
在执行这个的时候我遇到了一个报错
1
2
| E:\IDEA123\project\java-reflection\src\test\java\Base\Reflection\test1.java:37:40
java: 未报告的异常错误java.lang.ClassNotFoundException; 必须对其进行捕获或声明以便抛出
|
这是因为我们所用的字符串可能是找不到的类,所以必须允许异常抛出,而这种方法是最好加载我们的Runtime
的,对于forName
实际上有两个函数
Class.forName(className)
Class.forName(className, true, currentLoader)
虽然本质差不多,但是其实差的也挺多。默认情况下, forName 的第一个参数是类名;第⼆个参数表示是否初始化;第三个参数就是 ClassLoader(类的加载器)。是否初始化这个参数其实是告诉JVM是否执行”类初始化“,在 forName 的时候,构造函数并不会执行,而是执行类初始化,也就是会执行static{}
静态块里面的内容。
也就和我们第四种方法相互呼应,由指定 ClassLoader
加载类,不立即初始化(延迟至首次使用,也就是说不会执行静态块代码);适用于自定义类加载器、复杂模块加载场景。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| package Base.Reflection;
class Reflect1{
}
public class test1 {
public static void main(String[] args) throws Exception{
Class<?> clazz1 = Class.forName("java.lang.String");
System.out.println(clazz1.getName());
Class<?> clazz2 = Class.forName("Base.Reflection.Reflect1");
System.out.println(clazz2.getName());
}
}
/*
java.lang.String
Base.Reflection.Reflect1
|
第五种,TYPE
字段用于拿到原始类型(int
, double
…)的 Class
对象;在方法反射、重载分辨原始/包装类型时很有用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| package Base.Reflection;
public class test1 {
public static void main(String[] args){
Class<?> cInt1 = Integer.TYPE;
Class<?> cInt2 = int.class;
System.out.println(cInt1.getName());
System.out.println(cInt1 == cInt2);
Class<?> cDouble = Double.TYPE;
System.out.println(cDouble.getName());
System.out.println(Integer.class == Integer.TYPE);
}
}
/*
int
true
double
false
|
我们刚才所提到了一些代码块的问题,特别是static代码块由于加载方式不同导致是否执行,现在来了解一下代码块,静态初始化块(static block)、实例初始化块(instance initializer block) 和 构造方法(constructor),
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| package Base.Reflection;
class Reflect1{
public Reflect1() {
System.out.println("Constructor");
}
static {
System.out.println("Static");
}
{
System.out.println("Initialize");
}
}
public class test1 {
public static void main(String[] args){
new Reflect1();
}
}
/*
Static
Initialize
Constructor
|
- 静态初始化块(
static {}
)在类被加载到 JVM 时执行,仅执行一次,只能访问静态成员,同时在任何对象创建之前执行。 - 实例初始化块(
{}
)每次创建对象时,在构造方法之前执行。 - 构造方法(
public ClassName() {}
)每次创建对象时,在实例初始化块之后执行。
并且可以看到静态初始化块的优先级最高,当我们在其中利用恶意方法的话
1
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
| package Base.Reflection;
import java.io.IOException;
class Reflect1{
public Reflect1() {
System.out.println("Constructor");
}
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
System.out.println("Static");
}
{
System.out.println("Initialize");
}
}
public class test1 {
public static void main(String[] args) throws Exception{
Class<?> clazz2 = Class.forName("Base.Reflection.Reflect1");
Class<?> c2 = test1.class.getClassLoader().loadClass("Base.Reflection.Reflect1");
}
}
//hacked
|

创建对象
与刚才的new Reflect();
效果一直,只需要在获取对象之后进行.newInstance()
即可
1
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
| package Base.Reflection;
class Reflect2 {
public Reflect2() {
System.out.println("Constructor");
}
static {
System.out.println("hacked");
}
{
System.out.println("Initialize");
}
}
public class test2 {
public static void main(String[] args) throws Exception {
Class<?> clazz2 = Class.forName("Base.Reflection.Reflect2");
//Object obj = clazz2.newInstance();
Object obj = clazz2.getDeclaredConstructor().newInstance();
}
}
/*
hacked
Initialize
Constructor
|
访问字段
1
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
41
42
43
| package Base.Reflection;
import java.lang.reflect.Field;
class Person {
public String name = "baozongwi";
private int age = 20;
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
public class test3 {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("Base.Reflection.Person");
Object obj = clazz.getDeclaredConstructor().newInstance();
// 3. 获取public字段 name
Field nameField = clazz.getField("name");
System.out.println("原始 name = " + nameField.get(obj));
nameField.set(obj, "baozhongqi");
System.out.println("修改后 name = " + nameField.get(obj));
// 4. 获取private字段 age
Field ageField = clazz.getDeclaredField("age");
// 解除private限制
ageField.setAccessible(true);
System.out.println("原始 age = " + ageField.get(obj));
ageField.set(obj, 17);
System.out.println("修改后 age = " + ageField.get(obj));
System.out.println("最终对象: " + obj);
}
}
/*
原始 name = baozongwi
修改后 name = baozhongqi
原始 age = 20
修改后 age = 17
最终对象: Person{name='baozhongqi', age=17}
|
- clazz.getField(String name):获取公有字段(包括继承的)
- clazz.getDeclaredField(String name):获取本类中声明的字段(包括私有字段)
- field.setAccessible(true):允许访问私有字段
- field.get(Object obj):获取字段值
- field.set(Object obj, Object value):设置字段值
调用方法
调用方法和访问字段类似,
- clazz.getMethod(String name, Class… parameterTypes):获取 公有方法(包括继承的)(注意,这里第一个参数是方法名,后面的参数表示的是这个方法传入参数的类型,比如参数是String就是String.class)
- clazz.getDeclaredMethod(String name, Class… parameterTypes):获取 本类声明的方法(包括私有方法)
- method.setAccessible(true):允许访问私有方法
- method.invoke(Object obj, Object… args):调用方法,第一个参数是对象,后面是传入方法的参数
一般流程为,通过 Class
获取 Method
→ 如是私有则 setAccessible(true)
→ 如果是实例方法先创建对象 → 调用 invoke(对象, 参数...)
执行并返回结果。
1
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
41
42
43
44
| package Base.Reflection;
import java.lang.reflect.Method;
class Demo {
public void hello(String name) {
System.out.println("Hello, " + name);
}
private int add(int a, int b) {
return a + b;
}
public static void staticMethod() {
System.out.println("I am a static method!");
}
}
public class test4 {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("Base.Reflection.Demo");
Object obj = clazz.getDeclaredConstructor().newInstance();
// 3. 调用 public 实例方法 hello
Method helloMethod = clazz.getMethod("hello", String.class);
helloMethod.invoke(obj, "Alice");
// 4. 调用 private 实例方法 add
Method addMethod = clazz.getDeclaredMethod("add", int.class, int.class);
addMethod.setAccessible(true); // 必须解除限制
Object result = addMethod.invoke(obj, 5, 7);
System.out.println("add 方法返回值: " + result);
// 5. 调用 static 方法
Method staticM = clazz.getMethod("staticMethod");
// 静态方法不需要实例
staticM.invoke(null);
}
}
/*
Hello, Alice
add 方法返回值: 12
I am a static method!
|
此处我有一个非常疑惑的点,就是为什么静态方法不需要实例即可调用?
因为静态方法属于类本身,不属于对象,它在类加载的时候就放在 方法区(或者说 class 元数据里),跟对象实例没有关系。
后来我就在想啊?那只有在同一个文件里面吗?
不需要。静态方法和类绑定,只要你能拿到那个类的 Class<?>
对象,就能调用。在反射中,也就是随便调用了
calc
到这里,我们就已经学会了如何使用反射。先弹个计算器
1
2
3
4
5
6
7
8
9
10
11
12
13
| package Base.Reflection;
public class test5 {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec", String.class).invoke(clazz.newInstance(), "calc");
}
}
/*
Exception in thread "main" java.lang.IllegalAccessException: Class Reflection.test5 can not access a member of class java.lang.Runtime with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
at java.lang.Class.newInstance(Class.java:436)
at Reflection.test5.main(test5.java:6)
|
报错如下,显示私有,因为 java.lang.Runtime
的构造方法是 private 的,所以反射在尝试调用时直接抛出 IllegalAccessException
。
Runtime
采用了 单例模式,构造器私有,对外只暴露了一个 public static Runtime getRuntime()
方法,用来获取唯一实例。到这里我们就知道怎么改了
1
2
3
4
5
6
7
8
9
10
11
| package Base.Reflection;
public class test5 {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("java.lang.Runtime");
//clazz.getMethod("exec",String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz), "calc");
//clazz.getMethod("exec",String.class).invoke(clazz.getMethod("getRuntime").invoke(null), "calc");
Object runtime = clazz.getMethod("getRuntime").invoke(null);
clazz.getMethod("exec", String.class).invoke(runtime, "calc");
}
}
|
但是我们前面提到Runtime其实比较特殊,所以还会有两个问题
- 如果一个类没有无参构造方法,也没有类似单例模式里的静态方法,我们怎样通过反射实例化该类呢?
- 如果一个方法或构造方法是私有方法,我们是否能执行它呢?
对于问题一,只需要选一个合适的构造器来传参调用,有两种方法可以使用,第一种是走 List<String>
构造器
1
2
3
4
5
6
7
8
9
| package Base.Reflection;
public class test6 {
public static void main(String[] args) throws Exception {
Class<?> c = Class.forName("java.lang.ProcessBuilder");
Object pb = c.getConstructor(java.util.List.class).newInstance(java.util.Arrays.asList("calc"));
c.getMethod("start").invoke(pb);
}
}
|
第二种是走 String...
(即 String[]
)构造器,这里有一个坑点,需要强转 (Object)
或包一层 new Object[]{ ... }
,因为 Constructor#newInstance(Object... args)
本身是 varargs,如果你直接写 newInstance(new String[]{"calc.exe"})
,编译器可能把它当成“多个 Object 实参”的展开。
1
2
3
4
5
6
7
8
9
10
11
| package Base.Reflection;
public class test6 {
public static void main(String[] args) throws Exception {
Class<?> c = Class.forName("java.lang.ProcessBuilder");
Object pb = c.getConstructor(String[].class).newInstance((Object) new String[]{"calc"});
// Object pb = c.getConstructor(String[].class).newInstance(new Object[]{ new String[]{"calc"} });
// Object pb = c.getConstructor(String[].class).newInstance(new String[][]{{"calc"}});
c.getMethod("start").invoke(pb);
}
}
|
对于问题二的话,我们直接设置method.setAccessible(true)
即可
1
2
3
4
5
6
7
8
9
10
11
| package Base.Reflection;
public class test6 {
public static void main(String[] args) throws Exception {
Class<?> c = Class.forName("java.lang.Runtime");
java.lang.reflect.Constructor<?> m = c.getDeclaredConstructor();
m.setAccessible(true);
Object runtime = m.newInstance();
c.getMethod("exec", String.class).invoke(runtime, "calc");
}
}
|
原理
Java 反射依赖于 JVM 的类加载机制——它把 .class
文件的结构(字段/方法/构造器等元信息)映射成 Class
对象;再结合字节码结构,反射 API 使程序能在运行中动态操控这些结构,从而实现灵活调用和修改。
先说类加载,JVM 在运行时会按需加载类,加载过程分为三个阶段:加载(Loading) → 链接(Linking) → 初始化(Initialization)。
- 加载:JVM 通过类加载器找到
.class
文件的二进制表示,并为它创建一个 Class
对象。这个对象承载着类的完整元数据。 - 链接:
- 验证:确保字节码结构符合规范;
- 准备:为静态变量分配空间;
- 解析:将符号引用替换为直接引用。
- 初始化:执行
<clinit>
方法,也就是静态代码块和静态变量初始化逻辑。
另外,类加载器层级(如 Bootstrap、Extension、Application)采用父加载器委派模型,确保基础类加载安全、避免重复加载、避免篡改核心类。
对反射而言:调用 Class.forName("com.foo.Bar")
,就会触发上述所有阶段,从而生成可用于反射的 Class
对象,再通过它操作方法、字段、构造器等。
而类字节码如何与反射所联系呢?
JVM 在加载 .class
文件后,将这些元数据装载进 Class
对象内部。反射 API(如 getDeclaredMethod()
等)读取这些信息,而实际执行则通过 JVM 的 native 或动态生成的代码(如 JIT 编译)来完成。
此外,字节码操作框架(如 ASM、BCEL)还能在运行时修改类定义,实现 AOP、注入、代理等高级功能。
Java 源代码经编译器处理后,生成 .class
文件,其内容包括类的全部结构信息。
.class
文件结构大致包括:
- 魔数(CAFEBABE)
- 版本号
- 常量池(class、方法、字段、字符串等引用)
- 访问标志(如 public、abstract)
- 类声明、父类、接口
- 字段表(fields)
- 方法表(methods)
- 附加属性(attributes)
字节码是“.class 文件”的执行指令集,包括单字节操作码(opcode) + 可变长度的操作数,例如:aload_0
, invokespecial
, getfield
等。