Java 反射机制详解:原理、Class 类、类加载与反射调用优化

欢迎你来读这篇博客,这篇博客主要系统梳理 Java 反射机制。

本文会从反射是什么开始,逐步讲到反射原理、Class 类、获取 Class 对象的方式、动态加载与静态加载、类加载流程、反射获取类结构、反射创建对象、操作属性、调用方法,以及反射调用优化。

序言

在 Java 中,我们平时写代码通常是这样的:

1
2
3
User user = new User();
user.setName("Mario");
System.out.println(user.getName());

这种方式属于普通调用,也就是在编译阶段就已经知道要操作哪个类、哪个方法、哪个属性。

而反射的能力在于:

程序可以在运行期间获取类的信息,并动态创建对象、访问属性、调用方法。

也就是说,反射让 Java 程序拥有了“运行时观察和操作自身结构”的能力。

很多框架都大量使用了反射,例如:

  • Spring IOC 创建 Bean
  • Spring MVC 参数绑定
  • MyBatis 结果集映射
  • Jackson / Fastjson 序列化与反序列化
  • JUnit 测试方法扫描
  • Lombok、MapStruct 等工具在编译期或运行期处理类结构
  • 注解驱动开发

反射是 Java 框架的地基之一,理解反射之后,再看 Spring、MyBatis、ORM、RPC 框架都会清晰很多。

正文

一、Java 反射机制是什么

1.1 反射的定义

Java 反射机制是指:

在程序运行期间,动态获取类的信息,并动态创建对象、访问属性、调用方法的一种机制。

正常情况下,我们操作一个类需要在编译期明确知道它的类型。

例如:

1
2
User user = new User();
user.sayHello();

而反射可以这样做:

1
2
3
4
5
6
Class<?> clazz = Class.forName("com.demo.User");

Object obj = clazz.getDeclaredConstructor().newInstance();

Method method = clazz.getDeclaredMethod("sayHello");
method.invoke(obj);

这里的类名、方法名都可以来自配置文件、数据库、网络请求或注解扫描。

这就是反射最核心的价值:

把原本写死在代码里的调用关系,变成运行时动态决定。

1.2 反射能做什么

反射主要可以完成以下事情:

能力 说明
获取 Class 对象 获取某个类在 JVM 中的运行时描述
获取构造器 获取 public、private 构造方法
创建对象 即使构造器是 private,也可以通过反射创建
获取属性 获取类中的字段信息
操作属性 读取或修改对象字段
获取方法 获取类中的普通方法、私有方法、静态方法
调用方法 动态调用指定方法
获取注解 读取类、字段、方法、参数上的注解
获取父类和接口 分析类的继承结构
绕过访问限制 通过 setAccessible(true) 访问私有成员

二、反射原理图

反射的核心入口是 Class 对象。

Java 中每一个被 JVM 加载的类,都会在内存中对应一个唯一的 Class 对象。这个 Class
对象保存了该类的结构信息,包括类名、父类、接口、构造方法、字段、方法、注解等。

flowchart TD
    A[Java 源文件 User.java] --> B[编译生成 User.class 字节码]
    B --> C[类加载器 ClassLoader 加载字节码]
    C --> D[JVM 方法区保存类元信息]
    D --> E[生成 Class 对象]
    E --> F[获取构造器 Constructor]
    E --> G[获取属性 Field]
    E --> H[获取方法 Method]
    E --> I[获取注解 Annotation]
    F --> J[创建对象]
    G --> K[读写字段]
    H --> L[调用方法]

可以简单理解为:

反射不是凭空操作类,而是通过 JVM 中已经存在的 Class 元信息来操作类。

三、反射相关类

Java 反射相关类主要位于:

1
2
java.lang
java.lang.reflect

常见核心类如下。

所属包 作用
Class java.lang 表示类或接口的运行时信息
Constructor java.lang.reflect 表示构造方法
Field java.lang.reflect 表示成员变量
Method java.lang.reflect 表示成员方法
Modifier java.lang.reflect 解析修饰符
Array java.lang.reflect 动态创建和操作数组
Parameter java.lang.reflect 表示方法参数
Annotation java.lang.annotation 表示注解
AccessibleObject java.lang.reflect FieldMethodConstructor 的父类,可设置访问权限

3.1 Class

Class 是反射的入口。

1
Class<?> clazz = User.class;

通过 Class 可以获取类的所有结构信息。

3.2 Constructor

Constructor 表示构造方法。

1
2
Constructor<User> constructor = User.class.getDeclaredConstructor(String.class);
User user = constructor.newInstance("Mario");

3.3 Field

Field 表示类中的属性。

1
Field field = User.class.getDeclaredField("name");

3.4 Method

Method 表示类中的方法。

1
Method method = User.class.getDeclaredMethod("sayHello");

3.5 Modifier

Modifier 用于判断修饰符。

1
2
3
4
5
int modifiers = field.getModifiers();

System.out.println(Modifier.isPrivate(modifiers));
System.out.println(Modifier.isStatic(modifiers));
System.out.println(Modifier.isFinal(modifiers));

四、Class 类和常用方法

4.1 Class 是什么

Class 类是 Java 反射机制的核心类。

当一个 .class 文件被类加载器加载到 JVM 后,JVM 会为这个类创建一个 Class 对象。这个对象描述了类的完整结构。

例如:

1
Class<User> clazz = User.class;

这个 clazz 就可以理解为 User 类在 JVM 中的运行时说明书。

4.2 Class 常用方法

方法 作用
getName() 获取完整类名
getSimpleName() 获取简单类名
getPackageName() 获取包名
getSuperclass() 获取父类
getInterfaces() 获取实现的接口
getModifiers() 获取修饰符
getDeclaredFields() 获取本类声明的所有字段
getFields() 获取 public 字段,包括父类
getDeclaredMethods() 获取本类声明的所有方法
getMethods() 获取 public 方法,包括父类
getDeclaredConstructors() 获取本类所有构造器
getConstructors() 获取 public 构造器
getDeclaredAnnotations() 获取本类声明的注解
newInstance() 创建对象,已不推荐使用
getDeclaredConstructor().newInstance() 推荐的反射创建对象方式

示例:

1
2
3
4
5
6
7
8
9
10
11
12
public class ClassMethodDemo {

public static void main(String[] args) {
Class<User> clazz = User.class;

System.out.println("完整类名:" + clazz.getName());
System.out.println("简单类名:" + clazz.getSimpleName());
System.out.println("包名:" + clazz.getPackageName());
System.out.println("父类:" + clazz.getSuperclass());
System.out.println("修饰符:" + Modifier.toString(clazz.getModifiers()));
}
}

五、获取 Class 对象的六种方式

5.1 方式一:类名.class

这是最常用、最安全的方式。

1
Class<User> clazz = User.class;

特点:

  • 编译期已知具体类
  • 不会触发类初始化
  • 类型安全
  • 常用于框架内部或普通业务代码

5.2 方式二:对象.getClass()

通过已有对象获取 Class 对象。

1
2
User user = new User();
Class<?> clazz = user.getClass();

特点:

  • 必须先有对象
  • 适合运行时根据对象反查类型

5.3 方式三:Class.forName()

通过完整类名获取 Class 对象。

1
Class<?> clazz = Class.forName("com.demo.User");

特点:

  • 需要传入全限定类名
  • 默认会触发类加载和初始化
  • 常用于配置文件驱动、框架扫描、JDBC 加载驱动

例如:

1
Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver");

5.4 方式四:类加载器 ClassLoader

1
2
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?> clazz = classLoader.loadClass("com.demo.User");

特点:

  • 只加载类,通常不会立即初始化类
  • 更适合框架、中间件、插件化场景

5.5 方式五:基本类型的 TYPE

基本数据类型可以通过包装类的 TYPE 获取 Class 对象。

1
2
3
4
Class<Integer> intClass = int.class;
Class<Integer> intType = Integer.TYPE;

System.out.println(intClass == intType); // true

常见写法:

1
2
3
Class<?> c1 = int.class;
Class<?> c2 = boolean.class;
Class<?> c3 = double.class;

5.6 方式六:数组对象获取 Class

数组也有对应的 Class 对象。

1
2
3
4
String[] arr = new String[10];
Class<?> clazz = arr.getClass();

System.out.println(clazz.getName()); // [Ljava.lang.String;

也可以直接:

1
Class<?> clazz = String[].class;

5.7 六种方式对比

方式 示例 是否需要对象 是否常用 备注
类名.class User.class 编译期确定
对象.getClass() user.getClass() 运行期获取
Class.forName() Class.forName("com.demo.User") 默认触发初始化
ClassLoader classLoader.loadClass(...) 框架常用
基本类型 TYPE Integer.TYPE 一般 基本类型使用
数组.class String[].class 一般 数组类型使用

六、动态加载和静态加载

6.1 静态加载

静态加载是指在编译期间就明确依赖某个类。

1
User user = new User();

特点:

  • 编译期必须能找到 User
  • 如果类不存在,编译就会失败
  • 调用效率高
  • 类型安全

6.2 动态加载

动态加载是指在运行期间通过类名加载类。

1
2
Class<?> clazz = Class.forName("com.demo.User");
Object obj = clazz.getDeclaredConstructor().newInstance();

特点:

  • 编译期不需要直接依赖目标类
  • 运行期决定加载哪个类
  • 适合插件化、框架、配置驱动
  • 灵活性高,但性能和安全性需要注意

6.3 静态加载和动态加载对比

对比项 静态加载 动态加载
加载时机 编译期确定 运行期确定
类依赖 强依赖 弱依赖
类型安全 较低
灵活性 较低
性能 较好 反射调用有额外成本
典型场景 普通业务开发 框架、插件、配置驱动

6.4 示例:配置文件驱动类加载

假设配置文件中配置了类名:

1
2
className=com.demo.UserService
methodName=execute

Java 代码:

1
2
3
4
5
6
7
8
9
10
11
Properties properties = new Properties();
properties.load(new FileInputStream("application.properties"));

String className = properties.getProperty("className");
String methodName = properties.getProperty("methodName");

Class<?> clazz = Class.forName(className);
Object object = clazz.getDeclaredConstructor().newInstance();

Method method = clazz.getDeclaredMethod(methodName);
method.invoke(object);

这就是典型的动态加载思想。

Spring、MyBatis 这类框架,本质上也大量使用类似思想,只是实现复杂得多。

七、类加载流程图

Java 类从 .class 文件到可以被程序使用,大致要经历以下流程:

flowchart TD
    A[.java 源文件] --> B[javac 编译]
    B --> C[.class 字节码文件]
    C --> D[ClassLoader 加载]
    D --> E[加载 Loading]
    E --> F[验证 Verification]
    F --> G[准备 Preparation]
    G --> H[解析 Resolution]
    H --> I[初始化 Initialization]
    I --> J[Class 对象可用]
    J --> K[创建实例并调用方法]

类加载机制是反射的基础。反射能操作一个类,前提是这个类已经被 JVM 加载,并且 JVM 已经为它建立了运行时元信息。

八、类加载五个阶段

Java 类加载过程通常分为五个阶段:

1
加载 -> 验证 -> 准备 -> 解析 -> 初始化

其中验证、准备、解析三个阶段又统称为链接。

flowchart LR
    A[加载] --> B[链接]
    B --> C[初始化]

    subgraph B[链接]
        B1[验证]
        B2[准备]
        B3[解析]
    end

8.1 加载 Loading

加载阶段主要做三件事:

  1. 通过类的全限定名获取类的二进制字节流
  2. 将字节流中的静态存储结构转换为方法区的运行时数据结构
  3. 在内存中生成一个代表该类的 Class 对象

例如:

1
Class<?> clazz = Class.forName("com.demo.User");

当 JVM 第一次使用 User 类时,就会触发类加载。

8.2 验证 Verification

验证阶段的目的是确保 .class 文件的字节码是合法、安全的,不会危害 JVM。

验证内容包括:

  • 文件格式验证
  • 元数据验证
  • 字节码验证
  • 符号引用验证

简单说,就是 JVM 要确认这个类文件没有乱来。

8.3 准备 Preparation

准备阶段会为类变量分配内存,并设置默认初始值。

注意,这里是类变量,也就是 static 变量。

例如:

1
2
3
public class User {
public static int count = 10;
}

准备阶段:

1
count = 0;

初始化阶段才会执行:

1
count = 10;

所以准备阶段不是赋程序员写的值,而是赋 JVM 默认值。

8.4 解析 Resolution

解析阶段会把常量池中的符号引用转换为直接引用。

例如,代码里写的是:

1
UserService service;

在 class 文件中最初保存的是符号引用,解析后会转换为 JVM 能直接定位的内存引用。

可以粗略理解为:

1
符号名字 -> 实际内存位置

8.5 初始化 Initialization

初始化阶段会执行类构造器 <clinit>()

它主要包括:

  • 静态变量显式赋值
  • 静态代码块执行

例如:

1
2
3
4
5
6
7
public class User {
public static int count = 10;

static {
System.out.println("User 类初始化");
}
}

当类初始化时,会执行:

1
2
count = 10;
System.out.println("User 类初始化");

8.6 类初始化触发时机

常见触发类初始化的场景:

  • 创建类的实例
  • 访问类的静态变量
  • 调用类的静态方法
  • 使用 Class.forName()
  • 初始化子类时,父类会先初始化
  • JVM 启动时执行主类

示例:

1
Class.forName("com.demo.User"); // 通常会触发初始化

而下面这种方式通常只加载,不主动初始化:

1
2
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?> clazz = classLoader.loadClass("com.demo.User");

九、获取类结构信息

先准备一个测试类:

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
package com.demo.reflect;

public class User extends BaseUser implements Runnable {

private Long id;

public String name;

private Integer age;

public User() {
}

private User(Long id) {
this.id = id;
}

public User(Long id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}

private void privateMethod() {
System.out.println("private method");
}

public void sayHello(String message) {
System.out.println("hello " + message);
}

@Override
public void run() {
System.out.println("running");
}
}

父类:

1
2
3
4
5
6
7
8
9
10
package com.demo.reflect;

public class BaseUser {

public String baseField;

public void baseMethod() {
System.out.println("base method");
}
}

9.1 获取类名

1
2
3
4
5
Class<User> clazz = User.class;

System.out.println(clazz.getName());
System.out.println(clazz.getSimpleName());
System.out.println(clazz.getPackageName());

输出类似:

1
2
3
com.demo.reflect.User
User
com.demo.reflect

9.2 获取父类

1
2
Class<? super User> superclass = User.class.getSuperclass();
System.out.println(superclass.getName());

9.3 获取接口

1
2
3
4
5
Class<?>[] interfaces = User.class.getInterfaces();

for (Class<?> anInterface : interfaces) {
System.out.println(anInterface.getName());
}

9.4 获取构造器

获取 public 构造器:

1
2
3
4
5
Constructor<?>[] constructors = User.class.getConstructors();

for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}

获取本类所有构造器,包括 private:

1
2
3
4
5
Constructor<?>[] constructors = User.class.getDeclaredConstructors();

for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}

9.5 获取属性

获取 public 属性,包括父类:

1
2
3
4
5
Field[] fields = User.class.getFields();

for (Field field : fields) {
System.out.println(field.getName());
}

获取本类声明的所有属性,包括 private:

1
2
3
4
5
Field[] fields = User.class.getDeclaredFields();

for (Field field : fields) {
System.out.println(field.getName());
}

9.6 获取方法

获取 public 方法,包括父类和 Object 类方法:

1
2
3
4
5
Method[] methods = User.class.getMethods();

for (Method method : methods) {
System.out.println(method.getName());
}

获取本类声明的所有方法,包括 private:

1
2
3
4
5
Method[] methods = User.class.getDeclaredMethods();

for (Method method : methods) {
System.out.println(method.getName());
}

9.7 getXxx 和 getDeclaredXxx 的区别

方法 作用
getFields() 获取 public 字段,包括父类
getDeclaredFields() 获取本类声明的所有字段,不包括父类
getMethods() 获取 public 方法,包括父类
getDeclaredMethods() 获取本类声明的所有方法,不包括父类
getConstructors() 获取 public 构造器
getDeclaredConstructors() 获取本类声明的所有构造器

一句话记忆:

getXxx() 看 public 和继承,getDeclaredXxx() 看本类声明的所有成员。

十、反射爆破创建实例

“反射爆破”通常指通过反射绕过 Java 的访问修饰符限制,访问 private 构造器、属性或方法。

核心方法是:

1
setAccessible(true)

10.1 通过 public 无参构造创建对象

1
2
3
4
5
Class<User> clazz = User.class;

User user = clazz.getDeclaredConstructor().newInstance();

System.out.println(user);

推荐使用:

1
clazz.getDeclaredConstructor().newInstance();

不推荐使用:

1
clazz.newInstance();

因为 Class#newInstance() 已经过时,不利于处理异常,也只能调用 public 无参构造。

10.2 通过 public 有参构造创建对象

1
2
3
4
5
Constructor<User> constructor = User.class.getConstructor(Long.class, String.class, Integer.class);

User user = constructor.newInstance(1L, "Mario", 18);

System.out.println(user);

10.3 爆破 private 构造器创建对象

1
2
3
4
5
6
7
Constructor<User> constructor = User.class.getDeclaredConstructor(Long.class);

constructor.setAccessible(true);

User user = constructor.newInstance(1L);

System.out.println(user);

如果没有:

1
constructor.setAccessible(true);

调用 private 构造器时会抛出异常。

十一、反射爆破操作属性

11.1 操作 public 属性

1
2
3
4
5
6
7
8
9
User user = new User();

Field field = User.class.getField("name");

field.set(user, "Mario");

Object value = field.get(user);

System.out.println(value);

11.2 爆破 private 属性

1
2
3
4
5
6
7
8
9
10
11
User user = new User();

Field field = User.class.getDeclaredField("age");

field.setAccessible(true);

field.set(user, 18);

Object value = field.get(user);

System.out.println(value);

11.3 操作静态属性

假设类中有静态属性:

1
2
3
public class User {
private static String type = "normal";
}

反射操作:

1
2
3
4
5
6
7
8
9
Field field = User.class.getDeclaredField("type");

field.setAccessible(true);

field.set(null, "admin");

Object value = field.get(null);

System.out.println(value);

静态属性不属于某个具体对象,所以 get()set() 的对象参数可以传 null

11.4 操作 final 属性要谨慎

反射虽然可以尝试修改 final 字段,但不建议这么做。

原因:

  • JVM 可能对 final 字段做优化
  • 修改结果可能不符合预期
  • 破坏对象不可变性
  • 在高版本 JDK 和模块化环境下限制更多

实际业务开发中,不要依赖反射修改 final 字段。

十二、反射爆破操作方法

12.1 调用 public 方法

1
2
3
4
5
User user = new User();

Method method = User.class.getMethod("sayHello", String.class);

method.invoke(user, "Mario");

12.2 爆破 private 方法

1
2
3
4
5
6
7
User user = new User();

Method method = User.class.getDeclaredMethod("privateMethod");

method.setAccessible(true);

method.invoke(user);

12.3 调用有返回值的方法

假设有方法:

1
2
3
private String getInfo(String prefix) {
return prefix + ": " + name;
}

反射调用:

1
2
3
4
5
6
7
8
9
10
User user = new User();
user.name = "Mario";

Method method = User.class.getDeclaredMethod("getInfo", String.class);

method.setAccessible(true);

Object result = method.invoke(user, "user");

System.out.println(result);

12.4 调用静态方法

假设有静态方法:

1
2
3
private static void staticMethod() {
System.out.println("static method");
}

反射调用:

1
2
3
4
5
Method method = User.class.getDeclaredMethod("staticMethod");

method.setAccessible(true);

method.invoke(null);

静态方法不依赖对象,所以 invoke() 第一个参数可以传 null

12.5 Method.invoke 的注意点

Method.invoke() 的第一个参数是目标对象。

1
method.invoke(目标对象, 参数1, 参数2);

如果是实例方法:

1
method.invoke(user, "hello");

如果是静态方法:

1
method.invoke(null);

如果被调用方法抛出异常,反射会包装成:

1
InvocationTargetException

因此实际异常需要通过下面方式获取:

1
2
3
4
5
6
try {
method.invoke(user);
} catch (InvocationTargetException e) {
Throwable targetException = e.getTargetException();
targetException.printStackTrace();
}

十三、反射调用优化

反射很强,但它不是免费午餐。反射调用通常比普通方法调用慢,原因包括:

  • 需要运行时查找方法或字段
  • 需要进行访问权限检查
  • 参数需要封装成 Object 数组
  • 可能涉及装箱、拆箱
  • 反射调用不容易被 JVM 内联优化

13.1 优化一:缓存 Class、Method、Field、Constructor

不要在高频调用中反复查找反射对象。

不推荐:

1
2
3
4
for (int i = 0; i < 10000; i++) {
Method method = User.class.getDeclaredMethod("sayHello", String.class);
method.invoke(user, "Mario");
}

推荐:

1
2
3
4
5
6
Method method = User.class.getDeclaredMethod("sayHello", String.class);
method.setAccessible(true);

for (int i = 0; i < 10000; i++) {
method.invoke(user, "Mario");
}

框架中通常会缓存这些元信息。

例如:

1
private static final Map<String, Method> METHOD_CACHE = new ConcurrentHashMap<>();

13.2 优化二:减少 setAccessible 重复调用

setAccessible(true) 可以关闭 Java 语言访问检查,减少后续调用成本。

1
2
Method method = User.class.getDeclaredMethod("sayHello", String.class);
method.setAccessible(true);

注意:

  • 不要每次调用前都设置
  • 获取 Method 后设置一次即可
  • 在模块化环境下可能受到限制

13.3 优化三:使用 MethodHandle

MethodHandle 是 Java 7 引入的动态方法调用机制,位于:

1
java.lang.invoke

相比传统反射,MethodHandle 更接近 JVM 底层调用模型,在某些高频场景中性能更好。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

public class MethodHandleDemo {

public static void main(String[] args) throws Throwable {
User user = new User();

MethodHandles.Lookup lookup = MethodHandles.lookup();

MethodHandle handle = lookup.findVirtual(
User.class,
"sayHello",
MethodType.methodType(void.class, String.class)
);

handle.invoke(user, "Mario");
}
}

13.4 优化四:高频场景使用 LambdaMetafactory

对于极致性能场景,可以基于 LambdaMetafactory 把方法调用转换为函数式接口调用。

这种方式实现复杂,但性能较好。

一般业务开发中不必手写,很多框架会在内部做类似优化。

13.5 优化五:能不用反射就不用反射

反射适合框架、工具、通用组件,不适合普通业务逻辑滥用。

普通业务优先使用:

1
user.sayHello("Mario");

而不是:

1
method.invoke(user, "Mario");

反射的价值是动态性,不是替代普通调用。

13.6 反射优化建议总结

优化方式 说明
缓存反射对象 缓存 ClassFieldMethodConstructor
避免重复查找 不要在循环里频繁 getDeclaredMethod()
设置访问权限 对需要爆破的成员提前 setAccessible(true)
使用 MethodHandle 高频动态调用可考虑
控制使用范围 普通业务不要滥用反射
框架层封装 把反射封装在基础设施层

十四、反射练习

14.1 练习一:通过反射创建对象并设置属性

要求:

  1. 通过类名字符串加载 User
  2. 创建对象
  3. 设置 private 属性 name
  4. 调用 sayHello 方法

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ReflectPractice01 {

public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("com.demo.reflect.User");

Object user = clazz.getDeclaredConstructor().newInstance();

Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(user, "Mario");

Method method = clazz.getDeclaredMethod("sayHello", String.class);
method.invoke(user, "Java");
}
}

14.2 练习二:打印一个类的所有字段和方法

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
public class ReflectPractice02 {

public static void main(String[] args) {
Class<User> clazz = User.class;

System.out.println("字段信息:");
Field[] fields = clazz.getDeclaredFields();

for (Field field : fields) {
String modifier = Modifier.toString(field.getModifiers());
String type = field.getType().getSimpleName();
String name = field.getName();

System.out.println(modifier + " " + type + " " + name);
}

System.out.println("方法信息:");
Method[] methods = clazz.getDeclaredMethods();

for (Method method : methods) {
String modifier = Modifier.toString(method.getModifiers());
String returnType = method.getReturnType().getSimpleName();
String name = method.getName();

System.out.println(modifier + " " + returnType + " " + name);
}
}
}

14.3 练习三:通过配置文件调用方法

配置文件:

1
2
className=com.demo.reflect.User
methodName=sayHello

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ReflectPractice03 {

public static void main(String[] args) throws Exception {
Properties properties = new Properties();
properties.load(new FileInputStream("application.properties"));

String className = properties.getProperty("className");
String methodName = properties.getProperty("methodName");

Class<?> clazz = Class.forName(className);

Object object = clazz.getDeclaredConstructor().newInstance();

Method method = clazz.getDeclaredMethod(methodName, String.class);

method.invoke(object, "reflection");
}
}

这个练习就是很多框架的简化版:

通过配置决定加载哪个类,通过反射决定调用哪个方法。

14.4 练习四:简单实现一个对象属性拷贝工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class BeanCopyUtil {

public static void copyProperties(Object source, Object target) throws Exception {
Class<?> sourceClass = source.getClass();
Class<?> targetClass = target.getClass();

Field[] sourceFields = sourceClass.getDeclaredFields();

for (Field sourceField : sourceFields) {
sourceField.setAccessible(true);

String fieldName = sourceField.getName();
Object value = sourceField.get(source);

try {
Field targetField = targetClass.getDeclaredField(fieldName);
targetField.setAccessible(true);
targetField.set(target, value);
} catch (NoSuchFieldException e) {
// 目标对象没有该字段,忽略
}
}
}
}

测试:

1
2
3
4
5
6
7
8
9
10
User source = new User();
User target = new User();

Field nameField = User.class.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(source, "Mario");

BeanCopyUtil.copyProperties(source, target);

System.out.println(nameField.get(target));

注意:这只是练习版。真实项目中建议使用成熟工具,例如 Spring BeanUtils、MapStruct、MapStructPlus 等。

十五、反射常见异常

异常 说明
ClassNotFoundException 找不到指定类
NoSuchMethodException 找不到指定方法
NoSuchFieldException 找不到指定字段
InstantiationException 实例化失败
IllegalAccessException 没有访问权限
InvocationTargetException 被调用方法内部抛出异常
SecurityException 安全限制导致反射失败

示例:

1
2
3
4
5
try {
Method method = User.class.getDeclaredMethod("notExistMethod");
} catch (NoSuchMethodException e) {
System.out.println("方法不存在");
}

十六、反射的优缺点

16.1 优点

反射的优点:

  • 灵活性高
  • 可以运行时动态加载类
  • 可以解耦编译期依赖
  • 支持配置化、注解化、插件化开发
  • 是很多 Java 框架的基础

16.2 缺点

反射的缺点:

  • 性能低于直接调用
  • 破坏封装性
  • 编译期无法检查方法和字段是否存在
  • 代码可读性相对较差
  • 高版本 JDK 下可能受到模块化访问限制
  • 使用不当会带来安全风险

16.3 使用建议

反射适合:

  • 框架开发
  • 通用工具封装
  • 注解扫描
  • 插件化系统
  • 对象映射
  • 序列化与反序列化
  • 动态代理

反射不适合:

  • 普通业务逻辑中到处使用
  • 高频性能敏感路径中随意调用
  • 为了炫技绕过封装
  • 修改 final 字段或系统核心类

十七、反射梳理

mindmap
  root((Java 反射))
    核心入口
      Class
    获取 Class 对象
      类名.class
      对象.getClass
      Class.forName
      ClassLoader.loadClass
      基本类型.TYPE
      数组.class
    类结构信息
      类名
      包名
      父类
      接口
      修饰符
      注解
    反射成员
      Constructor
      Field
      Method
    反射操作
      创建对象
      操作属性
      调用方法
      获取注解
    类加载
      加载
      验证
      准备
      解析
      初始化
    调用优化
      缓存反射对象
      setAccessible
      MethodHandle
      LambdaMetafactory
      减少滥用
    应用场景
      Spring
      MyBatis
      Jackson
      JUnit
      插件化
      动态代理

再用一张表总结核心 API。

目标 常用 API
获取 Class User.classobj.getClass()Class.forName()
获取构造器 getConstructor()getDeclaredConstructor()
创建对象 constructor.newInstance()
获取字段 getField()getDeclaredField()
设置字段 field.set(object, value)
读取字段 field.get(object)
获取方法 getMethod()getDeclaredMethod()
调用方法 method.invoke(object, args)
绕过访问限制 setAccessible(true)
获取修饰符 getModifiers()Modifier
获取注解 getAnnotation()getDeclaredAnnotations()

总结

Java 反射机制的本质是:

通过 Class 对象,在运行时获取类的结构信息,并动态创建对象、操作属性、调用方法。

它的核心流程可以理解为:

1
类加载 -> 生成 Class 对象 -> 获取类结构 -> 创建对象 / 操作属性 / 调用方法

反射最大的价值是动态性和解耦能力。Spring、MyBatis、Jackson、JUnit 等框架之所以能做到自动扫描、自动装配、自动映射、自动调用,很大程度上都离不开反射。

但反射不是万能工具。它会带来性能损耗,也会破坏封装性。普通业务代码中,能直接调用就直接调用;框架、工具、通用组件中,才更适合使用反射。

反射让 Java 程序可以在运行时“看见自己”,甚至“改变自己”。能力很强,但别乱挥,容易打到自己。


Java 反射机制详解:原理、Class 类、类加载与反射调用优化
https://allendericdalexander.github.io/2026/06/11/java/java_reflection/
作者
AtLuoFu
发布于
2026年6月11日
许可协议