第 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(受检异常),必须处理。javaClass<?> 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% 不用 |
动手练习
- 动态加载:用
Class.forName加载java.util.HashMap,调用put方法 - 读配置:读一个
className=com.example.User的字符串,反射创建对象 - 批量操作:反射调用
List<User>的getName,拼成字符串
下一章:第 26 章:注解基础 →