前言
写 Java 代码时,你习惯了 new 一个对象、直接调方法------这些都在编译期 就确定了。但有时候,你需要在运行时才知道要操作哪个类:比如框架要读取配置文件来决定创建什么对象、IDE 的自动补全要列出类的方法、Spring 要扫描包并自动注入依赖。
这些需求都指向同一个机制------反射(Reflection)。它能让你在运行时动态地获取类的信息、创建对象、调用方法、访问字段------就像给程序装了一面"镜子",让它能看清自己。
一、概念:什么是反射
1.1 从一个问题开始
正常情况下,你写 Java 代码是这样的:
java
// 编译期就确定了:创建一个 User 对象,调用 getName 方法
User user = new User();
String name = user.getName();
但如果类名和方法名来自配置文件呢?
properties
# config.properties
class.name=net.feixiang.reflection.User
method.name=getName
编译时你根本不知道 class.name 是什么------只有运行时读了配置文件才知道。这时候就需要反射:
java
// 从配置文件读取类名和方法名
String className = props.getProperty("class.name"); // "net.feixiang.reflection.User"
String methodName = props.getProperty("method.name"); // "getName"
// 运行时动态加载类、创建对象、调用方法------这就是反射
Class<?> clazz = Class.forName(className);
Object obj = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getMethod(methodName);
String result = (String) method.invoke(obj);
1.2 反射的定义
一句话理解:反射是 Java 在运行时动态获取类信息、操作类成员(构造方法、字段、方法)的机制------让程序能"自省"和"自操作"。
Java 是静态语言,所有类型的确定都发生在编译期。反射打破了这一限制,让 Java 拥有了部分动态语言的能力。每一个被 JVM 加载的类,在内存中都对应一个 Class 对象------反射就是通过这些 Class 对象来获取和操作类的内部结构。
二、性质:Class 类与获取类信息
2.1 获取 Class 对象的四种方式
java
// 方式一:类名.class ------ 编译期就知道类
Class<User> clazz1 = User.class;
// 方式二:对象.getClass() ------ 已经有对象了
User user = new User();
Class<? extends User> clazz2 = user.getClass(); // 返回实际运行时类型
// 方式三:Class.forName("全限定类名") ------ 类名来自字符串(最常用)
Class<?> clazz3 = Class.forName("net.feixiang.reflection.User");
// 注意:这种方式必须提供完整的包名+类名,且类必须在 classpath 中
// 方式四:类加载器 ------ 更底层的方式
Class<?> clazz4 = ClassLoader.getSystemClassLoader().loadClass("net.feixiang.reflection.User");
2.2 从 Class 对象能获取什么
java
package net.feixiang.reflection;
import java.io.Serializable;
@ReflectInfo("用户实体类")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
public int age;
private static final String TYPE = "HUMAN";
public User() { }
private User(String name) {
this.name = name;
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
private void setName(String name) { this.name = name; }
public static void printType() {
System.out.println(TYPE);
}
}
java
// 获取 Class 对象
Class<User> userClass = User.class;
// 1. 基本信息
String className = userClass.getName(); // 完整类名: net.feixiang.reflection.User
String simpleName = userClass.getSimpleName(); // 简单类名: User
Package pkg = userClass.getPackage(); // 包名: net.feixiang.reflection
int modifiers = userClass.getModifiers(); // 修饰符: 1 (public)
Class<?> superClass = userClass.getSuperclass(); // 父类: java.lang.Object
Class<?>[] interfaces = userClass.getInterfaces(); // 实现的接口 (1 个): Serializable
// 2. 构造方法
Constructor<?>[] constructors = userClass.getConstructors(); // Public 构造 (2 个): User() / User(String,int)
Constructor<User> constructor = userClass.getConstructor(String.class, int.class); // 指定双参构造: User(String,int)
Constructor<?>[] declaredConstructors = userClass.getDeclaredConstructors(); // 所有构造(含私有, 3 个)
// 3. 字段
Field[] fields = userClass.getFields(); // Public 字段(含继承, 1 个): int age
Field ageField = userClass.getField("age"); // 指定字段: age
Field[] declaredFields = userClass.getDeclaredFields(); // 所有字段(含私有, 4 个)
// 4. 方法
Method[] methods = userClass.getMethods(); // Public 方法(含继承, 11 个)
Method getNameMethod = userClass.getMethod("getName"); // 指定方法: getName
Method[] declaredMethods = userClass.getDeclaredMethods(); // 所有方法(含私有, 3 个)
// 5. 注解
Annotation[] annotations = userClass.getAnnotations(); // 类上的注解 (1 个): @ReflectInfo
2.3 getXxx vs getDeclaredXxx 的区别
| 方法 | 范围 | 包含私有? | 包含继承? |
|---|---|---|---|
getFields() |
public 字段 | 否 | 是 |
getDeclaredFields() |
本类所有字段 | 是 | 否 |
getMethods() |
public 方法 | 否 | 是 |
getDeclaredMethods() |
本类所有方法 | 是 | 否 |
getConstructors() |
public 构造 | 否 | 不适用 |
getDeclaredConstructors() |
本类所有构造 | 是 | 不适用 |
记忆技巧 :带
Declared的 = "声明在本类的" + 包含私有 + 不包含继承。
三、操作:反射的核心操作
3.1 动态创建对象
java
Class<User> userClass = User.class;
// 方式一:Class.newInstance() ------ 仅限无参构造(Java 9 已废弃)
// User user = userClass.newInstance();
// 方式二:Constructor.newInstance() ------ 推荐方式
// 无参构造
Constructor<User> noArgConstructor = userClass.getDeclaredConstructor();
User user1 = noArgConstructor.newInstance();
// 有参构造
Constructor<User> argConstructor = userClass.getDeclaredConstructor(String.class, int.class);
User user2 = argConstructor.newInstance("张三", 25);
// 包括私有构造(需要 setAccessible)
Constructor<User> privateConstructor = userClass.getDeclaredConstructor(String.class);
privateConstructor.setAccessible(true); // 绕过访问控制
User user3 = privateConstructor.newInstance("秘密创建");
3.2 访问和修改字段
java
User user = new User("张三", 25);
Class<? extends User> clazz = user.getClass();
// 访问 public 字段
Field ageField = clazz.getField("age");
int age = (int) ageField.get(user); // 获取 user 对象的 age 字段值
System.out.println("年龄:" + age); // 年龄:25
ageField.set(user, 30); // 修改 age 的值
System.out.println(user.age); // 30
// 访问 private 字段 ------ 需要 setAccessible(true)
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)); // 李四
// 访问 static 字段 ------ get/set 时对象参数传 null
Field typeField = clazz.getDeclaredField("TYPE");
typeField.setAccessible(true);
String type = (String) typeField.get(null); // static 字段,对象传 null
System.out.println("类型:" + type); // 类型:HUMAN
3.3 调用方法
java
User user = new User("张三", 25);
Class<? extends User> clazz = user.getClass();
// 调用 public 方法
Method getNameMethod = clazz.getMethod("getName");
String name = (String) getNameMethod.invoke(user);
System.out.println("姓名:" + name); // 姓名:张三
// 调用 private 方法 ------ 需要 setAccessible(true)
Method setNameMethod = clazz.getDeclaredMethod("setName", String.class);
setNameMethod.setAccessible(true); // 突破 private 限制
setNameMethod.invoke(user, "李四"); // 调用 user.setName("李四")
System.out.println(getNameMethod.invoke(user)); // 李四
// 调用 static 方法 ------ invoke 时对象参数传 null
Method printTypeMethod = clazz.getDeclaredMethod("printType");
printTypeMethod.setAccessible(true);
printTypeMethod.invoke(null); // 输出:HUMAN
// static 方法不依赖具体对象,所以第一个参数传 null
3.4 绕过泛型检查
反射可以突破编译期的泛型限制------这是它既强大又危险的一面:
java
// 正常情况:泛型限制只能放 String
List<String> list = new ArrayList<>();
list.add("hello");
// list.add(123); // 编译错误!Integer 不能放入 List<String>
// 用反射绕过泛型检查
Class<? extends List> listClass = list.getClass();
Method addMethod = listClass.getMethod("add", Object.class);
addMethod.invoke(list, 123); // 成功!整型被塞进了字符串列表
addMethod.invoke(list, true); // 布尔型也塞进去了
System.out.println(list); // [hello, 123, true]
// 但取出来要当心了:
// String s = list.get(1); // ClassCastException!里面是 Integer
四、场景:反射的典型应用
4.1 框架的核心------依赖注入
java
// 模拟 Spring 的依赖注入:扫描注解,自动创建对象并注入
public class SimpleContainer {
private Map<Class<?>, Object> beans = new HashMap<>();
// 注册并自动注入
public void register(Class<?> clazz) throws Exception {
// 1. 创建对象
Object instance = clazz.getDeclaredConstructor().newInstance();
// 2. 扫描所有字段,发现 @Autowired 注解就注入
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
field.setAccessible(true);
Class<?> fieldType = field.getType();
Object dependency = beans.get(fieldType); // 从容器获取依赖
field.set(instance, dependency); // 注入
}
}
// 3. 放入容器
beans.put(clazz, instance);
}
}
4.2 JDBC 驱动加载
java
// JDBC 连接数据库------通过反射加载驱动类
// 你只需要配置驱动类名,框架帮你加载
String driverClass = "com.mysql.cj.jdbc.Driver";
Class.forName(driverClass); // 触发驱动的 static 代码块,自动注册到 DriverManager
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mydb", "root", "password");
4.3 序列化与反序列化
java
// JSON 序列化:把对象转为 JSON 字符串
public class SimpleJsonSerializer {
// 序列化:遍历对象的所有字段,拼成 JSON
public static String toJson(Object obj) throws Exception {
Class<?> clazz = obj.getClass();
StringBuilder json = new StringBuilder("{");
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
field.setAccessible(true);
json.append("\"").append(field.getName()).append("\":");
Object value = field.get(obj);
if (value instanceof String) {
json.append("\"").append(value).append("\"");
} else {
json.append(value);
}
if (i < fields.length - 1) json.append(", ");
}
json.append("}");
return json.toString();
}
// 反序列化:从 JSON 字符串恢复对象
public static <T> T fromJson(String json, Class<T> clazz) throws Exception {
T obj = clazz.getDeclaredConstructor().newInstance();
// 解析 JSON 字符串的简单实现(仅演示反射的用途)
// 实际项目请用 Jackson、Gson 等成熟框架
json = json.replace("{", "").replace("}", "").replace("\"", "");
String[] pairs = json.split(",");
for (String pair : pairs) {
String[] kv = pair.split(":");
String key = kv[0].trim();
String value = kv[1].trim();
Field field = clazz.getDeclaredField(key);
field.setAccessible(true);
// 根据字段类型转换值
if (field.getType() == int.class || field.getType() == Integer.class) {
field.set(obj, Integer.parseInt(value));
} else if (field.getType() == String.class) {
field.set(obj, value);
}
// 其他类型...
}
return obj;
}
}
4.4 动态代理
java
// 接口:用户服务
public interface UserService {
void save(String name);
String findById(int id);
}
java
// 真实实现:用户服务
public class UserServiceImpl implements UserService {
@Override
public void save(String name) {
System.out.println("保存用户:" + name);
}
@Override
public String findById(int id) {
return "用户" + id;
}
}
java
// 动态代理:在所有方法前后打印日志
public class LoggingHandler implements InvocationHandler {
private Object target; // 被代理的真实对象
public LoggingHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(">>> 开始调用 " + method.getName());
long start = System.currentTimeMillis();
Object result = method.invoke(target, args); // 反射调用真实方法
long elapsed = System.currentTimeMillis() - start;
System.out.println("<<< " + method.getName() + " 完成,耗时 " + elapsed + "ms");
return result;
}
// 创建代理对象
public static <T> T createProxy(T target, Class<T> interfaceClass) {
return (T) Proxy.newProxyInstance(
interfaceClass.getClassLoader(),
new Class<?>[]{interfaceClass},
new LoggingHandler(target)
);
}
}
使用:
java
// 获取代理对象,使用方式不变,但所有方法调用都会自动打印日志
UserService proxy = LoggingHandler.createProxy(new UserServiceImpl(), UserService.class);
proxy.save("张三");
// >>> 开始调用 save
// 保存用户:张三
// <<< save 完成,耗时 0ms
String result = proxy.findById(1001);
// >>> 开始调用 findById
// <<< findById 完成,耗时 0ms
System.out.println(result); // 用户1001
五、举例:完整的代码示例
5.1 通用 Bean 拷贝工具
java
// 利用反射实现 Bean 属性拷贝(简易版 BeanUtils)
public class BeanCopyUtils {
/**
* 将源对象的同名、同类型字段拷贝到目标对象
*/
public static void copy(Object source, Object target) throws Exception {
Class<?> sourceClass = source.getClass();
Class<?> targetClass = target.getClass();
// 遍历目标对象的所有字段
for (Field targetField : targetClass.getDeclaredFields()) {
// 过滤 static 和 final
int mod = targetField.getModifiers();
if (Modifier.isStatic(mod) || Modifier.isFinal(mod)) {
continue;
}
String fieldName = targetField.getName();
try {
// 在源对象中找同名字段
Field sourceField = sourceClass.getDeclaredField(fieldName);
// 类型必须匹配
if (!targetField.getType().equals(sourceField.getType())) {
continue;
}
// 获取源字段值 -> 设置到目标字段
sourceField.setAccessible(true);
targetField.setAccessible(true);
Object value = sourceField.get(source);
targetField.set(target, value);
} catch (NoSuchFieldException e) {
// 源对象没有这个字段,跳过
}
}
}
}
java
// 源对象:从数据库查出来的 DTO
public class UserDTO {
private String name;
private int age;
private String email;
// 构造方法、getter/setter ...
public UserDTO(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
}
java
// 目标对象:返回给前端的 VO(多了 description 字段,少了 email)
public class UserVO {
private String name;
private int age;
private String description;
@Override
public String toString() {
return "UserVO{name='" + name + "', age=" + age + ", description='" + description + "'}";
}
}
使用:
java
UserDTO dto = new UserDTO("张三", 25, "zhangsan@feixiang.net");
UserVO vo = new UserVO();
BeanCopyUtils.copy(dto, vo);
System.out.println(vo);
// UserVO{name='张三', age=25, description='null'}
// name 和 age 被成功拷贝,description 和 email 因不匹配被跳过
5.2 注解驱动的测试框架
java
// 自定义注解:标记测试方法
@Retention(RetentionPolicy.RUNTIME)
@interface TestCase {
String description() default "";
}
java
// 被测试类:计算器
public class Calculator {
public int add(int a, int b) { return a + b; }
public int divide(int a, int b) { return a / b; }
}
java
// 测试运行器:扫描 @TestCase 方法,自动执行
public class TestRunner {
public static void run(Class<?> testClass) throws Exception {
Object instance = testClass.getDeclaredConstructor().newInstance();
int passed = 0;
int failed = 0;
for (Method method : testClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(TestCase.class)) {
TestCase annotation = method.getAnnotation(TestCase.class);
System.out.print("测试: " + method.getName()
+ (annotation.description().isEmpty() ? "" : " - " + annotation.description())
+ " ... ");
try {
method.invoke(instance);
System.out.println("通过");
passed++;
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
System.out.println("失败:" + (cause != null ? cause.getMessage() : e.getMessage()));
failed++;
} catch (Exception e) {
System.out.println("失败:" + e.getMessage());
failed++;
}
}
}
System.out.println("\n==========");
System.out.println("通过: " + passed + ", 失败: " + failed);
}
}
使用:
java
// 编写测试类
public class CalculatorTest {
private Calculator calculator = new Calculator();
@TestCase(description = "加法测试")
public void testAdd() {
int result = calculator.add(2, 3);
if (result != 5) throw new AssertionError("期望 5,实际 " + result);
}
@TestCase(description = "除法测试")
public void testDivide() {
int result = calculator.divide(10, 2);
if (result != 5) throw new AssertionError("期望 5,实际 " + result);
}
@TestCase(description = "除零测试-应抛出异常")
public void testDivideByZero() {
calculator.divide(10, 0); // 期望抛出 ArithmeticException
}
}
java
// 运行
TestRunner.run(CalculatorTest.class);
// 测试: testAdd - 加法测试 ... 通过
// 测试: testDivide - 除法测试 ... 通过
// 测试: testDivideByZero - 除零测试-应抛出异常 ... 失败:/ by zero
//
// ==========
// 通过: 2, 失败: 1
六、反例与注意事项
6.1 性能开销
反射比直接调用慢很多------每次反射调用都涉及类型检查、访问权限验证等额外步骤:
java
// 直接调用
user.getName(); // 纳秒级(~1-10 ns)
// 反射调用
Method method = clazz.getMethod("getName");
method.invoke(user); // 也是纳秒级,但慢几十到上百倍(~100-1000 ns)
// 注意:差距取决于 JVM 版本和优化程度,并非毫秒级
优化建议 :如果同一个反射方法要频繁调用,把 Method 对象缓存起来,调用时设置 setAccessible(true):
java
private static final Map<String, Method> METHOD_CACHE = new HashMap<>();
public static Method getCachedMethod(Class<?> clazz, String name, Class<?>... paramTypes)
throws NoSuchMethodException {
String key = clazz.getName() + "#" + name;
return METHOD_CACHE.computeIfAbsent(key, k -> {
try {
Method m = clazz.getDeclaredMethod(name, paramTypes);
m.setAccessible(true); // 关闭访问检查,减少性能开销
return m;
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
});
}
6.2 破坏封装性
反射可以绕过 private 限制,破坏面向对象的封装性:
java
// 反例:随意的 setAccessible(true) 绕过了类设计者的意图
class BankAccount {
private double balance = 1000;
// 唯一合法的取款方式------有校验
public void withdraw(double amount) {
if (amount > balance) throw new IllegalArgumentException("余额不足");
balance -= amount;
}
}
java
// 用反射直接修改,跳过所有业务逻辑
BankAccount account = new BankAccount();
Field balanceField = BankAccount.class.getDeclaredField("balance");
balanceField.setAccessible(true);
balanceField.set(account, -999999); // 余额被非法修改为负数!
反射是一把双刃剑------框架用它实现灵活性,但业务代码中滥用会破坏设计、引入安全风险。
6.3 类型安全丢失
反射操作绕过了编译期的类型检查,错误推迟到运行期:
java
// 编译期不检查类型
Method method = clazz.getMethod("setAge", int.class);
method.invoke(user, "不是整数"); // 编译通过!运行时 IllegalArgumentException
6.4 反射的局限性
- 修改
final字段行为未定义,强烈不建议尝试 :对于static final的基本类型和 String 常量,由于编译期内联,修改通常无效。 - 不能获取局部变量信息:方法内部的局部变量在反射中不可见。
- 不能获取泛型的实际类型参数(因为类型擦除) :
List<String>和List<Integer>在运行时都是List,通过反射看不到泛型参数。
七、速查清单
| 问题 | 答案 |
|---|---|
| 什么是反射? | 运行时动态获取类信息、操作类成员的机制 |
| 获取 Class 对象有哪些方式? | 类名.class、对象.getClass()、Class.forName()、类加载器 |
getFields() 和 getDeclaredFields() 的区别? |
前者只拿 public + 包含继承;后者拿本类全部 + 包含私有 |
| 怎么访问私有成员? | setAccessible(true) 后获取/调用 |
| 反射怎么创建对象? | Constructor.newInstance() |
| 反射怎么调用方法? | Method.invoke(对象, 参数...) |
| 反射怎么访问字段? | Field.get(对象) / Field.set(对象, 值) |
| 反射的主要应用场景? | 框架(Spring)、序列化(Jackson)、JDBC 驱动加载、动态代理、IDE |
| 反射的缺点是什么? | 性能开销大、破坏封装性、丢失编译期类型安全 |
| 如何优化反射性能? | 缓存 Method/Field 对象、关闭访问检查 setAccessible(true) |
Class.forName 和 ClassLoader.loadClass 的区别? |
前者会触发类的 static 初始化,后者不会 |
getModifiers() 返回什么? |
一个 int,通过位运算判断(如 Modifier.isPublic(mod)) |
八、面试口述:什么是反射
反射是 Java 在运行时动态操作类的能力------通过 Class 对象,你可以在程序运行过程中获取类的构造方法、字段、方法、注解等所有信息,并且可以创建对象、读写字段、调用方法,甚至绕过 private 访问限制。
获取 Class 对象的方式有四种:类名.class、对象.getClass()、Class.forName("全限定类名") 和类加载器。其中 Class.forName 最常用,因为它接受字符串参数,支持从配置文件动态加载类。
反射的核心操作有:通过 Constructor.newInstance() 创建对象;通过 Method.invoke(obj, args) 调用方法;通过 Field.get(obj) 和 Field.set(obj, value) 读写字段。要访问私有成员,需要先调用 setAccessible(true) 突破访问控制。
反射是框架的灵魂------Spring 的依赖注入、Jackson 的序列化、MyBatis 的结果映射、JDK 动态代理,底层都是反射。但反射也有缺点:第一,性能比直接调用慢数十倍,频繁调用需要缓存 Method/Field 对象;第二,它破坏了封装性,可以绕过 private 限制;第三,编译期的类型安全检查在反射中失效,错误推迟到运行期。所以反射的正确使用姿势是------框架底层用它,业务代码尽量不用。