Java 反射
反射(Reflection)让程序在运行时"看穿"自己:动态获取类的构造方法、方法、字段,甚至直接调用它们。IDE 提示、Spring、MyBatis、JSON 序列化底层都离不开反射。
获取 Class 对象的三种方式
要反射一个类,先拿到它的 Class 对象。同一个类只对应一个 Class,三种方式结果相同:
// 方式一:对象.getClass()
String s = "hi";
Class<?> c1 = s.getClass();
// 方式二:类名.class
Class<?> c2 = String.class;
// 方式三:Class.forName(全限定名)
Class<?> c3 = Class.forName("java.lang.String");
System.out.println(c1 == c2 && c2 == c3);
// 输出:true
方式三按字符串类名加载,类找不到时抛 ClassNotFoundException,需要 try-catch 或向上 throws。
反射创建对象并调用方法
先准备一个普通的目标类(放在与 main 同一个文件里即可):
class Person {
private String name;
public Person() { }
public Person(String name) { this.name = name; }
public void say() { System.out.println("你好," + name); }
private void secret() { System.out.println("私有方法被调用"); }
}
片段需放进 main 方法中运行(并 import java.lang.reflect.*):
Class<?> cls = Class.forName("Person");
Object p = cls.getConstructor(String.class).newInstance("小明");
cls.getMethod("say").invoke(p); // 调用 public 方法
// 输出:你好,小明
Method m = cls.getDeclaredMethod("secret");
m.setAccessible(true); // 绕过访问检查
m.invoke(p); // 调用 private 方法
// 输出:私有方法被调用
反射操作私有字段
字段用 getDeclaredField 获取,private 字段要 setAccessible(true) 后才能读写:
Field f = cls.getDeclaredField("name");
f.setAccessible(true); // 打开私有字段访问
f.set(p, "小红"); // 直接改字段值
cls.getMethod("say").invoke(p);
// 输出:你好,小红
方法、字段、构造器分别对应 Method、Field、Constructor 三个类,invoke()、get()/set()、newInstance() 完成实际动作。
反射的用途
- 框架与配置:Spring 按配置文件或注解里的类名,反射创建对象并注入依赖。
- 序列化 / ORM:Jackson、Gson、MyBatis 反射读写对象的字段。
- 动态代理:JDK 动态代理、AOP 切面底层用反射生成代理。
- 开发工具:IDE 自动补全、调试器查看对象结构。
性能与安全注意
- 性能:反射比直接调用慢(含解析与安全检查),高并发热点路径应避免;确需使用时缓存 Method/Field 对象可显著提速。
- 安全:setAccessible 能突破 private 限制,恶意代码可借此窥探内部;模块系统(JPMS)下反射也受到限制,生产环境应控制反射的使用范围。
反射是 Java 的"自省能力":三种方式拿到 Class,再通过 Constructor、Method、Field 动态创建与调用,甚至访问私有成员。它是框架的基石,但慢且越权,性能敏感处慎用、对外部输入尤其要防。