一、💡 一句话理解
核心结论
反射(Reflection)让 Java 程序在运行时查看一个类有哪些字段、方法和构造器,并在必要时创建对象、读取字段或调用方法。
平时写代码时,我们直接写 person.sayHello();反射则可以先通过字符串找到 sayHello 方法,再决定要不要调用它。
二、🧭 反射是什么
2.1 为什么需要反射
普通调用时,类名和方法名都在写代码时确定:
Person person = new Person("小明");
person.sayHello();有些场景下,程序运行前并不知道要处理哪个类或哪个方法。例如框架需要:
- 根据配置创建对象;
- 扫描带某个注解的方法;
- 将数据库查询结果填入对象字段;
- 根据请求地址调用对应的方法。
这时就需要反射。JDK 的 java.lang.reflect 包提供了运行时获取类、字段、方法、构造器信息并操作它们的能力。Java 反射包文档
2.2 反射中的四个核心对象
| 对象 | 它代表什么 | 常见用途 |
|---|---|---|
Class | 一个类本身 | 查看类名、获得字段、方法、构造器。 |
Field | 类中的一个字段 | 读取或修改对象属性。 |
Method | 类中的一个方法 | 调用指定方法。 |
Constructor | 类中的一个构造器 | 在运行时创建对象。 |
可以把它们理解为:Class 是一张“类的说明书”,其中列出了字段、方法和构造器。
三、⚙️ 最基础的反射操作
3.1 获取 Class 对象
Class 是反射的入口。最常用的是 .class:
public class ReflectionStartDemo {
public static void main(String[] args) {
// 先按普通方式创建 Person 对象并调用方法。
Person person = new Person("小明");
person.sayHello();
// 再取得 Person 类对应的 Class 对象。
Class<Person> personClass = Person.class;
System.out.println("类名:" + personClass.getName());
}
}
class Person {
private final String name;
public Person(String name) {
this.name = name;
}
public void sayHello() {
System.out.println(name + ":你好!");
}
}运行后会输出:
小明:你好!
类名:Person这里的 person 是一个具体的人对象,所以可以直接调用 person.sayHello();personClass 则是 Person 这个类的说明书对象,所以可以读取类名、查找方法或构造器。
也可以从已有对象获得:
Person person = new Person("小明");
Class<?> personClass = person.getClass();Class 的实例代表运行中的类或接口;JVM 会在加载类时创建相应的 Class 对象。Java Class 文档
这句话的意思是:当程序第一次需要使用 Person 这个类时,JVM 会把它编译后的 Person.class 文件读进内存,并自动创建一个 Class<Person> 对象,用来描述 Person。
这个 Class<Person> 不是 Person 的普通实例,而是“Person 类的说明书对象”。它记录了 Person 有哪些字段、方法和构造器,反射正是通过它查看类的结构。
Person.java
↓ 编译
Person.class
↓ JVM 加载
Class<Person> 对象
↓
反射查看 Person 的结构不需要自己创建 Class 对象;JVM 会自动创建。Person.class 或 person.getClass() 都是在取得这个已经存在的对象。
3.2 获取并调用方法
在上一个示例的 main 方法中,已经有了 person 和 personClass。下面的代码会查找 sayHello(),再通过反射执行它:
import java.lang.reflect.Method;
// 获取名为 sayHello、没有参数的公开方法。
Method method = personClass.getMethod("sayHello");
// 在 person 对象上执行这个方法。
method.invoke(person);因此,下面两行的效果相同:
person.sayHello(); // 编写代码时已确定要调用 sayHello
method.invoke(person); // 运行时根据方法名 "sayHello" 查找后再调用getMethod 查找公开方法;invoke 执行方法。若方法有参数,查找时要写参数类型,调用时要传参数值:
Method method = Person.class.getMethod("greet", String.class);
method.invoke(person, "小红");3.3 获取并读取字段
import java.lang.reflect.Field;
// 获取公开字段 age。
Field field = Person.class.getField("age");
// 从 person 对象上读取 age 的值。
int age = (int) field.get(person);getField 只查找公开字段。getDeclaredField 可以查找本类声明的字段,包括 private 字段,但访问私有字段需要额外处理,且会受到 Java 封装和模块规则限制。日常业务代码应优先使用正常的 getter/setter,不要为了方便滥用反射修改私有数据。
3.4 获取构造器并创建对象
import java.lang.reflect.Constructor;
// 获取参数为 String 的公开构造器。
Constructor<Person> constructor = Person.class.getConstructor(String.class);
// 用该构造器创建对象。
Person person = constructor.newInstance("小明");四、🚀 完整小例子:查看、创建和调用
4.1 前置准备
需要 JDK 8 或更高版本。新建空目录,并创建文件 ReflectionDemo.java。
4.2 完整代码
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionDemo {
/**
* 被反射操作的普通类。
*/
static class Person {
// 公开字段,便于演示 Field.get()。
public int age;
private final String name;
// 普通构造器,稍后会通过反射调用它。
public Person(String name) {
this.name = name;
}
// 普通方法,稍后会通过反射调用它。
public void sayHello() {
System.out.println(name + ":你好!");
}
// 带参数的方法,演示反射调用时如何传入参数。
public String greet(String guest) {
return name + " 对 " + guest + " 说:你好";
}
}
public static void main(String[] args) throws ReflectiveOperationException {
// 1. 获得 Person 类的 Class 对象。
Class<Person> personClass = Person.class;
System.out.println("类名:" + personClass.getName());
// 2. 普通方式创建对象,并直接调用方法。
Person person = new Person("小明");
System.out.println("普通调用:");
person.sayHello();
// 3. 找到 sayHello() 方法,并在 person 对象上通过反射调用。
Method sayHelloMethod = personClass.getMethod("sayHello");
System.out.println("反射调用:");
sayHelloMethod.invoke(person);
// 4. 找到 String 参数的构造器,并通过反射创建另一个 Person 对象。
Constructor<Person> constructor = personClass.getConstructor(String.class);
Person reflectedPerson = constructor.newInstance("小红");
// 5. 找到公开字段 age,并为反射创建的对象赋值、读取。
Field ageField = personClass.getField("age");
ageField.set(reflectedPerson, 18);
System.out.println("年龄:" + ageField.get(reflectedPerson));
// 6. 找到 greet(String) 方法,并在反射创建的对象上调用它。
Method greetMethod = personClass.getMethod("greet", String.class);
Object result = greetMethod.invoke(reflectedPerson, "小明");
System.out.println(result);
}
}4.3 启动和验证
在文件所在目录运行:
javac ReflectionDemo.java
java ReflectionDemo预期输出中的类名会包含外部类名:
类名:ReflectionDemo$Person
普通调用:
小明:你好!
反射调用:
小明:你好!
年龄:18
小红 对 小明 说:你好这个例子依次完成了:普通方式创建对象并调用方法 → 通过 Class 查找方法并调用 → 查找构造器并创建对象 → 查找字段并读写 → 调用带参数的方法。
五、🔗 反射和自定义注解的关系
反射依赖类加载机制先把类变成 JVM 中的 Class 对象;加载、链接、初始化以及类加载器委派过程可以参考 2. JVM的类加载机制。
前一篇 注解 中的 @NeedLogin 本身只是标签。反射可以在运行时检查某个方法有没有这个标签:
import java.lang.reflect.Method;
Method method = UserService.class.getMethod("viewProfile");
// true 表示该方法标了 @NeedLogin。
boolean needLogin = method.isAnnotationPresent(NeedLogin.class);
if (needLogin) {
System.out.println("调用前先检查是否登录");
}这就是“注解负责说明,程序负责执行”的实现基础:
方法写上 @NeedLogin
↓
反射取得 Method 对象
↓
isAnnotationPresent 判断是否有标签
↓
程序执行登录检查或其他规则要让运行时反射读到自定义注解,定义注解时必须写:
@Retention(RetentionPolicy.RUNTIME)六、📌 什么时候使用反射
反射适合用于“运行前无法完全确定要操作哪个类或方法”的情况:
| 场景 | 反射做什么 |
|---|---|
| 依赖注入框架 | 查找构造器、字段或方法,再注入对象。 |
| JSON 转对象 | 找到字段,把 JSON 数据写入对象。 |
| 注解处理 | 找到带特定注解的类或方法,执行对应规则。 |
| 测试工具 | 执行符合规则的测试方法。 |
如果类和方法在写代码时已明确,直接调用通常更简单、类型更安全,也更容易阅读;不要为了使用反射而使用反射。
七、📝 总结
- 反射让程序在运行时查看并操作类的结构。
Class是入口;Field、Method、Constructor分别对应字段、方法和构造器。getMethod/invoke可以查找并调用方法;getField/get/set可以读写公开字段。- 反射可以读取带
RUNTIME保留策略的自定义注解。
记忆句:普通代码直接调用;反射先找到,再调用。