






















学Java面向对象时,老师常说:"先定义类,再创建对象,然后调用方法。" 这很合理。
但后来我接触到一些框架(比如Spring、MyBatis),发现它们有个"邪门"的能力:
在运行时,它们能创建一个类的对象、调用它的方法,甚至修改私有字段的值——而这一切,事先根本不知道这个类是什么!
这是怎么做到的?
答案就是 Java反射机制(Reflection)。
一句话概括:在运行时动态地获取类的信息,并操作类或对象的能力。
正常写代码时,类的结构在编译期就确定了:
Student s = new Student(); // 编译时就知道有 Student 类
s.study(); // 编译时就知道有 study() 方法
但反射不一样,它让程序在运行时才决定去操作哪个类:
// 运行时才知道要操作 "Student" 这个类
Class<?> clazz = Class.forName("com.example.Student");
// 运行时才知道要调用 "study" 这个方法
Method method = clazz.getMethod("study");
method.invoke(clazz.newInstance());
Java的反射API主要集中在 java.lang.reflect 包下,核心就四个类:
| 类 | 作用 |
|---|---|
| Class | 代表一个类的"元信息",是反射的入口 |
| Field | 代表类的成员变量 |
| Method | 代表类的方法 |
| Constructor | 代表类的构造方法 |
假设我们有一个普通的类:
public class Person {
private String name;
public int age;
public Person() {}
private Person(String name) {
this.name = name;
}
public void sayHello() {
System.out.println("Hello, I'm " + name);
}
private void secret() {
System.out.println("This is private!");
}
}
// 方式1:类名.class(最常用,编译期检查)
Class<Person> clazz1 = Person.class;
// 方式2:对象.getClass()(已有对象时用)
Person p = new Person();
Class<? extends Person> clazz2 = p.getClass();
// 方式3:Class.forName()(动态加载,最灵活)
Class<?> clazz3 = Class.forName("com.example.Person");
// 获取所有 public 构造方法
Constructor<?>[] constructors = clazz.getConstructors();
// 获取指定构造方法(包括 private)
Constructor<Person> privateCon = clazz.getDeclaredConstructor(String.class);
privateCon.setAccessible(true); // 暴力破解访问权限!
Person p = privateCon.newInstance("Alice");
// 获取 public 方法(包括继承的)
Method sayHello = clazz.getMethod("sayHello");
sayHello.invoke(p); // 输出: Hello, I'm Alice
// 获取 private 方法
Method secret = clazz.getDeclaredMethod("secret");
secret.setAccessible(true);
secret.invoke(p); // 输出: This is private!
// 获取 public 字段
Field ageField = clazz.getField("age");
ageField.set(p, 20);
System.out.println(p.age); // 20
// 获取 private 字段
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(p, "Bob");
光会API不够,要知道什么时候用。反射的典型应用场景:
// Spring 读取配置文件后,大概是这样创建对象的:
Class<?> beanClass = Class.forName("com.example.UserService");
Object bean = beanClass.newInstance();
// 然后反射调用 set 方法注入依赖
Method setDao = beanClass.getMethod("setUserDao", UserDao.class);
setDao.invoke(bean, new UserDao());
InvocationHandler handler = (proxy, method, args) -> {
System.out.println("方法 " + method.getName() + " 被调用了");
return method.invoke(target, args);
};
反射很强大,但也有明显的缺点:
| 缺点 | 说明 |
|---|---|
| 性能损耗 | 反射调用比直接调用慢10~100倍(JVM难以优化) |
| 安全性问题 | setAccessible(true) 可以访问私有成员,破坏封装 |
| 编译期检查失效 | 反射调用的方法名写错了,编译不会报错,运行时才抛异常 |
| 代码可读性差 | 一堆字符串硬编码,IDE无法跳转,维护困难 |
使用建议:框架底层可以用,业务代码尽量别用。如果非用不可,做好缓存(Method、Field 对象可以复用)。
用反射来"打破"String的不可变性:
String s = "Hello";
Field valueField = String.class.getDeclaredField("value");
valueField.setAccessible(true);
char[] value = (char[]) valueField.get(s);
value[0] = 'h'; // 改成小写
System.out.println(s); // 输出: hello (理论上,但现代JDK有优化可能不生效)
⚠️ 警告:这只是实验!生产环境千万别这么干,String的不可变性是Java安全设计的基石。
反射是Java提供给程序员的"元能力"——让代码去操作代码本身。它是一把双刃剑:
用得好,能写出高度灵活、可扩展的框架
用不好,会让代码变成难以维护的"黑魔法"
作为学习者,理解反射的原理,是阅读Spring源码、理解动态代理、掌握AOP的必经之路。
📌 推荐阅读:
《深入理解Java虚拟机》第9章——类加载机制
Spring源码:AbstractAutowireCapableBeanFactory 中的 doCreateBean 方法
💬 互动:你在学习或使用反射时遇到过哪些坑?欢迎在评论区分享!
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。