欢迎光临
我们一直在努力

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)
    未经允许不得转载:171主机测评 » Java 反射从入门到实战(附面试八股 + 企业级案例)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址