Java 反射从入门到实战(附面试八股 + 企业级案例)
一、反射是什么?🤔
1. 核心定义
Java 反射(Reflection) 是指在程序运行时,动态获取类的完整结构信息(构造器、方法、字段、注解等),并能调用这些成员的机制。
- 正常编码:编译期就固定类名、方法名,如
new Student().setId(1) - 反射编码:运行时才确定要操作的类和方法,如
Class.forName("Student").getMethod("setId").invoke(obj, 1)
一句话总结:反射让代码在运行时 “照镜子”,看清自身结构并动态操作,是框架底层的核心基石。
2. 核心价值
- 动态性:无需在编译期写死类名,可通过配置 / 字符串动态加载类
- 通用性:一套代码可操作任意类,适合开发通用工具、框架
- 穿透性:可突破访问权限限制,操作 private 成员(需谨慎使用)
二、基础篇:反射入口 —— 获取 Class 对象
要操作类,必须先获取 Class 对象,它是反射的唯一入口。
1. 三种获取 Class 的方式
java
运行
public class GetClassDemo {
public static void main(String[] args) throws ClassNotFoundException {
// 方式1:类名.class(编译期安全,最常用)
Class<String> cls1 = String.class;
// 方式2:对象.getClass()(必须先创建对象)
String str = "hello";
Class<? extends String> cls2 = str.getClass();
// 方式3:Class.forName("全类名")(最灵活,框架底层首选)
Class<?> cls3 = Class.forName("java.lang.String");
// 同一个类在 JVM 中只有一个 Class 对象
System.out.println(cls1 == cls2); // true
System.out.println(cls2 == cls3); // true
}
}
表格
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 类名.class | 编译期检查,无异常,性能高 | 需提前知道类名,不够灵活 | 日常开发、工具类 |
| 对象.getClass () | 基于已有对象获取 | 必须先创建对象,无法操作未实例化类 | 已有对象场景 |
| Class.forName() | 最灵活,可通过字符串动态加载 | 需处理 ClassNotFoundException | 框架底层、配置驱动 |
2. 面试八股
Q:为什么同一个类的 Class 对象是唯一的?A:JVM 类加载机制保证,一个类只会被加载一次,生成唯一的
Class实例存储在方法区,因此无论通过哪种方式获取,都指向同一个对象。
Q:Class.forName () 和 类名.class 的区别?A:
Class.forName()会触发类的静态代码块执行,而类名.class不会触发类初始化,仅加载类信息。
三、进阶篇:解剖类的核心结构
拿到 Class 对象后,即可动态获取并操作类的三大核心成员:字段(Field)、方法(Method)、构造器(Constructor)。
1. 操作字段(Field)
java
运行
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
/**
* 反射操作字段(Field)示例
* 核心:1. 获取类的所有字段 2. 暴力访问private字段
*/
class Student {
// 私有字段
private int id;
// 公有字段
public String name;
}
public class FieldDemo {
public static void main(String[] args) {
try {
// 1. 获取Student的Class对象
Class<?> cls = Student.class;
// 2. 创建Student实例(用于字段赋值/读取)
Student s = new Student();
// ========== 第一步:获取并打印所有字段信息 ==========
// getDeclaredFields():获取本类所有字段(含private,不含父类)
Field[] allFields = cls.getDeclaredFields();
System.out.println("===== Student类所有字段信息 =====");
for (Field f : allFields) {
// 1. 获取字段修饰符(如private、public)
String modifier = Modifier.toString(f.getModifiers());
// 2. 获取字段类型(简化类名,如int、String)
String type = f.getType().getSimpleName();
// 3. 获取字段名
String fieldName = f.getName();
System.out.println("修饰符:" + modifier + " | 类型:" + type + " | 字段名:" + fieldName);
}
// ========== 第二步:暴力访问private字段 ==========
// getDeclaredField("id"):获取指定名称的字段(含private)
Field idField = cls.getDeclaredField("id");
// 关键:关闭Java权限检查,允许操作private字段(暴力反射)
idField.setAccessible(true);
// 给s对象的id字段赋值100
idField.set(s, 100);
// 读取s对象的id字段值
int idValue = (int) idField.get(s);
System.out.println("\n===== 操作private字段结果 =====");
System.out.println("Student对象的id值:" + idValue); // 输出100
} catch (NoSuchFieldException e) {
// 字段不存在时的异常处理
System.err.println("错误:找不到指定字段");
e.printStackTrace();
} catch (IllegalAccessException e) {
// 未关闭权限检查时操作private字段的异常处理
System.err.println("错误:无权访问该字段(需调用setAccessible(true))");
e.printStackTrace();
} catch (Exception e) {
// 兜底异常处理
System.err.println("反射操作字段失败:" + e.getMessage());
e.printStackTrace();
}
}
}
2. 操作方法(Method)
java
运行
class Student {
private int id;
public void setId(int id) { this.id = id; }
public int getId() { return id; }
}
public class MethodDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Student.class;
Student s = new Student();
// 获取所有方法(含 private)
Method[] allMethods = cls.getDeclaredMethods();
for (Method m : allMethods) {
System.out.println("方法名:" + m.getName());
System.out.println("返回值:" + m.getReturnType().getSimpleName());
System.out.println("参数:" + Arrays.toString(m.getParameterTypes()));
}
// 动态调用方法
Method setId = cls.getMethod("setId", int.class);
setId.invoke(s, 200); // 等价于 s.setId(200)
Method getId = cls.getMethod("getId");
int id = (int) getId.invoke(s); // 等价于 s.getId()
System.out.println(id); // 200
}
}
3. 操作构造器(Constructor)
java
运行
class Student {
private int id;
private String name;
public Student() {}
public Student(int id, String name) {
this.id = id;
this.name = name;
}
}
public class ConstructorDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Student.class;
// 无参构造创建对象(Java 9+ 推荐,替代过时的 Class.newInstance())
Constructor<?> con1 = cls.getConstructor();
Object obj1 = con1.newInstance();
// 有参构造创建对象
Constructor<?> con2 = cls.getConstructor(int.class, String.class);
Object obj2 = con2.newInstance(1, "张三");
}
}
4. 面试八股
Q:反射能访问 private 成员吗?A:可以,通过
setAccessible(true)关闭 Java 权限检查,即可读写 private 字段、调用 private 方法,但这破坏了封装性,需谨慎使用。
Q:Class.newInstance () 为什么被废弃?A:Java 9+ 中
Class.newInstance()被标记为过时,因为它只能调用无参构造,且无法处理构造器抛出的受检异常,推荐使用getConstructor().newInstance()。
四、实战篇:企业级案例 —— 动态对象赋值
结合实际需求,实现通过反射批量创建 Student 对象并赋值,贴近框架底层实现逻辑。
1. 需求背景
给定 ids 和 names 数组,利用反射创建 5 个 Student 对象,赋值后存入 Object[] 并打印。
2. 基础版实现(硬编码方法名)
java
运行
class Student {
private int id;
private String name;
public void setId(int id) { this.id = id; }
public void setName(String name) { this.name = name; }
@Override
public String toString() {
return "Student{id=" + id + ", name='" + name + "'}";
}
}
public class ReflectionBasicDemo {
public static void main(String[] args) throws Exception {
int[] ids = {1,2,3,4,5};
String[] names = {"张三","李四","王五","赵六","钱七"};
Class<?> cls = Student.class;
Object[] objs = new Object[5];
for (int i = 0; i < 5; i++) {
// 创建对象
Object obj = cls.getConstructor().newInstance();
// 赋值 id
Method setId = cls.getMethod("setId", int.class);
setId.invoke(obj, ids[i]);
// 赋值 name
Method setName = cls.getMethod("setName", String.class);
setName.invoke(obj, names[i]);
// 存入数组
objs[i] = obj;
}
// 打印结果
for (Object o : objs) {
System.out.println(o);
}
}
}
3. 进阶版实现(框架风格,通用匹配)
java
运行
public class ReflectionAdvancedDemo {
public static void main(String[] args) throws Exception {
int[] ids = {1,2,3,4,5};
String[] names = {"张三","李四","王五","赵六","钱七"};
Class<?> cls = Student.class;
Object[] objs = new Object[5];
Method[] methods = cls.getDeclaredMethods(); // 获取所有方法
for (int i = 0; i < 5; i++) {
Object obj = cls.getConstructor().newInstance();
int id = ids[i];
String name = names[i];
// 遍历方法,自动匹配 setter 方法
for (Method m : methods) {
if (m.getName().startsWith("set")) { // 筛选 setter 方法
Class<?> paramType = m.getParameterTypes()[0];
// 根据参数类型自动赋值
if (paramType == int.class) {
m.invoke(obj, id);
} else if (paramType == String.class) {
m.invoke(obj, name);
}
}
}
objs[i] = obj;
}
// 打印结果
System.out.println("总共有:" + objs.length + "个学生对象");
for (Object o : objs) {
System.out.println(o);
}
}
}
4. 代码对比
表格
| 版本 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 基础版 | 逻辑简单,易理解 | 方法名硬编码,扩展性差 | 入门练习、简单业务 |
| 进阶版 | 通用性强,可适配任意类 | 遍历方法有轻微性能开销 | 框架底层、通用工具 |
五、高级篇:反射的应用场景与坑点
1. 核心应用场景
- 框架底层:Spring IOC(动态创建 Bean)、MyBatis(数据库结果映射到对象)
- 通用工具:对象拷贝(BeanUtils)、JSON 序列化(Jackson/Gson)
- 动态代理:AOP 切面编程、事务管理
- 配置驱动:通过 XML / 注解配置,动态加载类和方法
2. 反射的坑点(面试高频)
- 性能损耗:反射调用比直接调用慢 10~100 倍,避免在高频循环中使用
- 封装破坏:可访问 private 成员,违反面向对象封装原则
- 编译期安全丢失:拼写错误(如方法名写错)只能在运行时发现
- API 过时:
Class.newInstance()已废弃,需使用getConstructor().newInstance() - 安全风险:
setAccessible(true)可能导致敏感数据泄露
3. 面试八股
Q:反射为什么性能差?A:1. 反射调用需要动态解析方法签名,无法享受 JIT 编译优化;2. 权限检查、参数装箱拆箱带来额外开销;3. 无法内联优化,调用栈更深。
Q:反射和动态代理的关系?A:动态代理(JDK 动态代理)底层依赖反射实现,通过反射获取目标类的方法,在运行时生成代理类并增强方法逻辑。
六、总结学习路径
- 入门:掌握 3 种获取
Class的方式,理解反射核心概念 - 进阶:熟练使用
Field/Method/Constructor操作类成员 - 实战:实现通用对象赋值工具,理解框架底层反射逻辑
- 深入:研究 Spring/MyBatis 源码,掌握反射在企业级框架中的应用
七、文末思考
反射是 Java 进阶的核心知识点,也是面试高频考点。它赋予了 Java 极强的动态性,但也带来了性能和安全问题。日常开发尽量少用反射,框架开发则必须精通反射—— 理解反射,才能真正读懂 Spring、MyBatis 等框架的底层逻辑。
更多推荐




所有评论(0)