反射入门(Class、Method、Field)
概述
反射(Reflection) 是 Java 在运行时检查或修改类、方法、字段等程序行为的能力。通过反射,可以在编译期未知具体类型的情况下,根据类名加载类、创建实例、调用方法、读写字段。许多框架(如 Spring、JUnit、序列化库)都依赖反射实现「配置驱动」或「约定优于配置」;学习反射有助于理解这类框架的工作原理。
反射的入口:Class 对象
在 Java 中,每个已加载的类在 JVM 里都有且仅有一个 Class 对象,它是反射的入口。要反射操作某个类,首先需要获取它的 Class 实例。
获取 Class 的三种常见方式
| 方式 | 适用场景 | 示例 |
|---|---|---|
类名.class | 编译期已知类型 | String.class、User.class |
对象.getClass() | 已有对象实例 | "hello".getClass() |
Class.forName("全限定类名") | 类名来自配置或字符串 | Class.forName("java.util.ArrayList") |
// 1. 类名.class —— 编译期已知类型
Class<String> stringClass = String.class;
// 2. 对象.getClass() —— 已有实例
String s = "hello";
Class<?> c1 = s.getClass(); // 与 String.class 是同一 Class 实例
// 3. Class.forName —— 类名是字符串(可能来自配置文件等)
Class<?> c2 = Class.forName("java.util.ArrayList");注意
Class.forName("全限定类名") 会抛出受检异常 ClassNotFoundException,必须用 try-catch 或 throws 处理。若类不存在或未在类路径中,会在此处报错。
通过 Class 获取构造方法、方法与字段
拿到 Class<?> 后,可以获取该类的构造方法、方法和字段,再在运行时调用或访问。
常用 API 一览
- 构造方法:
getConstructor(参数类型...)、getDeclaredConstructor(...)、newInstance(...) - 方法:
getMethod("方法名", 参数类型...)、getDeclaredMethod(...)、method.invoke(对象, 实参...) - 字段:
getField("字段名")、getDeclaredField(...)、field.get(对象)、field.set(对象, 值)
带 Declared 的方法会返回本类声明的成员(包括 private),但不包括继承的;不带 Declared 的只返回 public 成员(包括继承的)。
基本用法
1. 获取并调用构造方法创建实例
通过 getDeclaredConstructor(参数类型...) 获取构造方法,再调用 newInstance(实参...) 创建对象。
import java.lang.reflect.Constructor;
public class ReflectionConstructDemo {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("java.util.ArrayList");
// 获取无参构造方法
Constructor<?> cons = clazz.getDeclaredConstructor();
Object list = cons.newInstance();
System.out.println(list); // []
}
}若构造方法带参数,需传入对应的 Class 数组:
// 例如获取 String 的 byte[] 构造方法
Constructor<String> cons = String.class.getDeclaredConstructor(byte[].class);
String s = cons.newInstance(new byte[]{72, 101, 108, 108, 111});2. 获取并调用方法(Method)
通过 getMethod("方法名", 参数类型...) 或 getDeclaredMethod(...) 得到 Method,再通过 method.invoke(对象, 实参...) 调用。静态方法传入的「对象」可为 null。
import java.lang.reflect.Method;
public class ReflectionMethodDemo {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("java.util.ArrayList");
Object list = clazz.getDeclaredConstructor().newInstance();
// 获取 add(Object) 方法并调用
Method add = clazz.getMethod("add", Object.class);
add.invoke(list, "A");
add.invoke(list, "B");
System.out.println(list); // [A, B]
// 获取 size() 方法并调用(无参)
Method size = clazz.getMethod("size");
int n = (Integer) size.invoke(list);
System.out.println(n); // 2
}
}3. 获取并读写字段(Field)
通过 getField("字段名") 或 getDeclaredField("字段名") 得到 Field。私有字段默认不可访问,需要先 field.setAccessible(true)(会受模块系统与安全管理器约束)。读写使用 field.get(对象) 和 field.set(对象, 值)。
import java.lang.reflect.Field;
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
// 在其它类中通过反射读写 User 的私有字段
public class ReflectionFieldDemo {
public static void main(String[] args) throws Exception {
User user = new User("张三", 20);
Class<?> clazz = user.getClass();
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true); // 允许访问 private
String name = (String) nameField.get(user);
System.out.println(name); // 张三
nameField.set(user, "李四");
System.out.println(nameField.get(user)); // 李四
}
}提示
setAccessible(true) 会破坏封装,仅在对测试、框架或兼容旧代码等场景下合理使用;生产代码中应优先通过正常 API 访问。
使用示例
示例 1:根据类名创建实例并调用无参方法
适合「类名来自配置」的简单工厂场景。
public static Object createAndCall(String className, String methodName) throws Exception {
Class<?> clazz = Class.forName(className);
Object obj = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getMethod(methodName);
return method.invoke(obj);
}示例 2:列出类的所有公开方法
便于调试或简单工具。
public static void printPublicMethods(Class<?> clazz) {
for (Method m : clazz.getMethods()) {
System.out.println(m.getReturnType().getSimpleName() + " " + m.getName());
}
}示例 3:通过反射调用私有方法(慎用)
测试或特殊兼容场景下,可能需要对私有方法做可访问性设置后再调用。
Method privateMethod = clazz.getDeclaredMethod("internalHelper", String.class);
privateMethod.setAccessible(true);
Object result = privateMethod.invoke(instance, "arg");注意事项
注意
- 性能:反射调用比直接调用慢,且不易被 JIT 优化。高频路径上应避免用反射。
- 封装:
setAccessible(true)会绕过访问检查,破坏封装,仅在有充分理由时使用。 - 类型安全:反射在编译期无法做类型检查,
invoke、get、set的返回值与参数多为Object,需要强转,易在运行时出现ClassCastException。
注意
使用 Class.forName 或 getMethod/getDeclaredMethod 时,若类名或方法签名写错,会在运行时抛出 ClassNotFoundException、NoSuchMethodException 等,需做好异常处理或校验配置。
相关链接
- 注解 — 注解常与反射配合,通过反射读取注解信息
- 常用类(Object、包装类、Math) —
getClass()即获取 Class 的一种方式 - Oracle Java 教程 - 反射
- Java API - java.lang.reflect