Skip to content
第 25 章 后端 ⏱ 8 分钟阅读

第 25 章:反射基础 ​

学习目标 ​

  • 知道反射是干什么的
  • 会用 Class.forName 动态加载类
  • 知道反射的代价,别滥用

一、反射是干什么的 ​

反射 = 运行时拿到类的信息,动态操作对象。

正常情况下,我们写代码时类名是写死的:

java
User u = new User();
u.setName("Tom");

反射可以在运行时才决定是哪个类、调用哪个方法:

java
// 变量 clazz 在运行时才确定具体是哪个类
Class<?> clazz = Class.forName("com.example.User");
Object obj = clazz.getDeclaredConstructor().newInstance();
// 动态调用方法
clazz.getMethod("setName", String.class).invoke(obj, "Tom");

什么时候用反射?

  • 框架(Spring / MyBatis / Spring Boot)给你做的
  • 你写业务代码 99% 不会直接用

二、Class 对象(反射的入口) ​

每个类在 JVM 加载后,都有一个唯一的 Class 对象。三种获取方式:

java
// ① Class.forName(全限定名)— 用的最多
Class<?> c1 = Class.forName("java.util.ArrayList");

// ② 类名.class
Class<?> c2 = ArrayList.class;

// ③ 对象.getClass()
ArrayList<String> list = new ArrayList<>();
Class<?> c3 = list.getClass();

⚠️ 坑 1:Class.forName 抛 ClassNotFoundException(受检异常),必须处理。

java
Class<?> c = Class.forName("com.example.User");
// ❌ 编译报错:未处理的异常
// ✅ 包一层 try-catch

三、动态创建对象 ​

java
Class<?> c = Class.forName("com.example.User");

// ① 调用无参构造
Object obj = c.getDeclaredConstructor().newInstance();

// ② 调用有参构造
Object obj2 = c.getDeclaredConstructor(String.class, int.class)
               .newInstance("Tom", 18);

⚠️ 坑 2:newInstance() 自 JDK 9 起已废弃,推荐用 getDeclaredConstructor().newInstance()。

java
// ❌ 旧 API
Object obj = c.newInstance();
// ✅ 新 API
Object obj = c.getDeclaredConstructor().newInstance();

四、动态读 / 改字段 ​

java
class User {
    private String name = "Tom";
}

User u = new User();
Class<?> c = u.getClass();

// 拿到 private 字段
Field f = c.getDeclaredField("name");
f.setAccessible(true);               // ⚠️ 突破 private 限制(慎用!)

String oldName = (String) f.get(u);    // 读:"Tom"
f.set(u, "Jerry");                     // 改:"Jerry"
System.out.println(u.getName());       // "Jerry"

五、动态调用方法 ​

java
class User {
    public void setName(String name) { this.name = name; }
    private String greet(String s) { return "Hi, " + s; }
}

User u = new User();
Class<?> c = u.getClass();

// ① 调用 public 方法
Method m1 = c.getMethod("setName", String.class);
m1.invoke(u, "Tom");

// ② 调用 private 方法
Method m2 = c.getDeclaredMethod("greet", String.class);
m2.setAccessible(true);
String result = (String) m2.invoke(u, "Tom");    // "Hi, Tom"

六、反射能做什么 / 不能做什么 ​

能做不能做
运行时拿类名、字段、方法大量调用(慢 10-100 倍)
动态创建对象突破 final?(不能)
动态调用方法破坏封装
动态读 private 字段编译期类型检查

⚠️ 坑 3:final 字段即使是 private,反射可以改值,但对基本类型无效(Integer 也不可)。

java
// ✅ 可以
String s = "abc", Field f = String.class.getDeclaredField("value");
f.setAccessible(true);  // 绕过,但生产用会炸
// ❌ 不可
Integer i = 100; Field f = Integer.class.getDeclaredField("value");
// Integer 不可变,反射改不了

七、反射的代价 ​

维度性能
反射调用比直接调用慢 10-100 倍
大量循环一次加载 + 缓存 Method / Field
第一次访问还要触发类加载
java
// ❌ 循环里每次 getMethod
for (int i = 0; i < 10000; i++) {
    Method m = c.getMethod("toString");   // 每次都查
    m.invoke(obj);
}

// ✅ 提前缓存
Method m = c.getMethod("toString");
for (int i = 0; i < 10000; i++) {
    m.invoke(obj);
}

⚠️ 坑 4:热循环里别用反射。Method.invoke 每次都要做参数包装 / 权限检查。

八、什么时候用反射 ​

场景推荐
写业务代码❌ 别用,直接 new
写框架 / 工具✅ 必要(读取注解、动态代理)
测试✅ 偶尔(Mockito 底层就是反射)
序列化 / JSON 解析✅ 必要(Jackson / Gson 靠反射)

九、不用反射怎么搞? ​

大部分场景,写代码时类名是知道的,直接用就完事:

java
// ❌ 反射
Class<?> c = Class.forName("com.example.User");
Object obj = c.getDeclaredConstructor().newInstance();

// ✅ 直接 new
User obj = new User();

真正需要反射的场景:

  • 类的名字在配置文件里(框架根据配置加载)
  • 一个接口有多个实现,根据参数动态选(RPC 框架)
  • 注解处理(扫描 classpath 找带 @Component 的类)

十、本章小结 ​

要点关键
反射入口Class.forName / 类名.class / obj.getClass()
拿字段getDeclaredField + setAccessible
调方法getMethod + invoke
构造对象getDeclaredConstructor().newInstance()
代价慢 10-100 倍,热循环别用
用法框架用,业务代码 99% 不用

动手练习 ​

  1. 动态加载:用 Class.forName 加载 java.util.HashMap,调用 put 方法
  2. 读配置:读一个 className=com.example.User 的字符串,反射创建对象
  3. 批量操作:反射调用 List<User> 的 getName,拼成字符串

下一章:第 26 章:注解基础 →

本站基于 VitePress 构建 · 由 StackHub 团队维护