说明:练习今日新学知识点——反射 Field(属性/字段/变量)(Demo01:Class 类获取 Field 对象的 4 个 API——getFields / getField / getDeclaredFields / getDeclaredField、Field 类的常用方法——get() 读取 / set() 赋值 / setAccessible(true) 暴力反射 / getType() 类型 / getName() 属性名 / getModifiers() 修饰符、Modifier.toString 解析修饰符、getDeclaredFields 遍历输出全部字段的修饰符/类型/属性名、getField + get/set 读取修改 public 属性、getDeclaredField + setAccessible(true) 暴力反射读取修改 private 属性、操作静态属性 get/set 对象参数传 null、反射赋值与普通访问是同一份数据、ORM 框架用 Field.set() 把数据库数据映射到 Java 类的底层原理)+ 反射 Method(方法)(Demo02:Class 类获取 Method 对象的 4 个 API——getMethods / getMethod / getDeclaredMethods / getDeclaredMethod、重载方法必须传参数类型区分、Method 类的常用方法——invoke 调用 / setAccessible(true) 暴力反射 / getReturnType / getParameterTypes / getModifiers、invoke 四种调用规律——实例方法传对象 / 静态方法传 null / void 返回 null / private 先暴力反射、动态代理 / 测试框架 / 注解解释器的基础)。练习一~四:Field 字段反射——通用工具遍历打印全部字段;读取/修改 public 字段;暴力反射读取/修改 private 字段;操作静态字段 + 模拟 ORM 映射(综合实战)。练习五~八:Method 方法反射——通用工具遍历打印全部方法;反射调用实例方法(无参/带参/重载);暴力反射调用私有方法与静态方法;模拟测试框架反射三件套综合实战。
难度:⭐⭐⭐
知识点:getDeclaredFields()(本类全部字段,含 private、不含父类)、field.getModifiers() + Modifier.toString() 解析修饰符、field.getType() + getSimpleName() 获取类型、field.getName() 获取属性名、clazz.getSimpleName() 获取简单类名
场景描述:课堂 Demo01 演示① 在 main 方法里写死了"打印 Student 的全部字段"。但反射最大的价值是通用——本练习把"打印一个类所有字段信息"封装成通用工具方法 printFields(Class<?> clazz),传入任意类的 Class 对象,就能打印它的全部字段(修饰符 + 类型 + 属性名)。再分别对 Student 和 String(JDK 核心类!)调用,体会"反射不写死具体类、谁传谁打"。
// 源码:course/Student.java(本练习的实体类,7 个字段:4 private + 2 public + 1 public static)
public class Student{
private Integer id;
private String name;
private Integer age;
private String gender;
public String email;
public String phone;
public static String school;
// …… 4 个构造方法 + study() 方法
}
写一个通用方法,传入任意类的 Class 对象,遍历打印其所有字段的「修饰符 + 类型 + 属性名」,分别对 Student.class 和 String.class 调用验证。
printFields(Class<?> clazz)——参数是任意类的 Class 对象clazz.getDeclaredFields() 获取本类声明的全部字段数组(含 private,不含父类)getModifiers() 拿修饰符 int 值 → Modifier.toString() 解析成可读字符串(如 private / public static)getType().getSimpleName() 获取类型名 + getName() 获取属性名修饰符 + 空格 + 类型 + 空格 + 属性名printFields(Student.class) 和 printFields(String.class) 调用package course;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class FieldPrintUtilTest {
public static void main(String[] args) {
// 1. 打印 Student 的全部字段
// 2. 打印 String 的全部字段(JDK 核心类,看看它有哪些字段)
}
// 通用工具:打印任意类 clazz 的所有字段(修饰符 + 类型 + 属性名)
public static void printFields(Class<?> clazz) {
// 打印类名(clazz.getSimpleName())
// getDeclaredFields() 获取全部字段数组
// 遍历每个字段:
// getModifiers() + Modifier.toString() 解析修饰符
// getType().getSimpleName() 获取类型名
// getName() 获取属性名
// 打印
}
}
输出示例:
Student 类的所有字段:
private Integer id
private String name
private Integer age
private String gender
public String email
public String phone
public static String school
String 类的所有字段:
private final byte[] value
private final byte coder
private int hash
private boolean hashIsZero
private static final long serialVersionUID
...
选做加分:
getFields() 再打印一遍,对比与 getDeclaredFields() 的差异——Student 的哪些字段消失(private 的),getFields() 是否列出父类 Object 的字段(Object 没有 public 字段,可观察 List/Date 等有父类字段的类对比)getDeclaredFields().length(Student 应为 7)getModifiers() 的原始 int 值(如 private=2、public=1、public static=9),再用 Modifier.toString() 对比——体会"修饰符底层是 int 位掩码"思考题:
getDeclaredFields() 和 getFields() 有什么区别?(提示:Declared 拿本类全部含 private 不含父类;非 Declared 只拿 public 含父类继承——口诀"含 Declared 的能拿私有")getModifiers() 返回的是 int,为什么需要 Modifier.toString() 解析?它能解析出哪些字符串?(提示:private / public / public static...)String.class 是 JDK 核心类,为什么反射也能看到它的字段?(提示:反射对任意类都适用,包括 JDK 核心类)难度:⭐⭐⭐
知识点:clazz.getField("字段名") 获取指定 public 字段、field.get(对象) 读取属性值、field.set(对象, 值) 修改属性值、反射赋值与普通访问是同一份数据
场景描述:课堂 Demo01 演示② 用 getField("email") + get()/set() 读取、修改了 Student 的 public 字段 email。本练习要求:用反射读取/修改 Student 的 email、phone 两个 public 字段,修改后用普通方式 对象.字段名 直接访问验证(证明反射赋值与普通访问操作的是同一份数据),并把"读取/修改字段"封装成通用方法 readField / setField。
创建 Student 对象后,用反射读取 public 字段 email、phone,再反射修改它们,并用普通访问方式验证修改生效。
new Student(1001, "张三", 25, "男", "zhangsan@163.com", "13636363366") 创建对象(六参构造,email/phone 构造里已赋值)clazz.getField("email") 获取 email 字段的 Field 对象(getField 只能拿 public 字段)emailField.get(s) 读取值、emailField.set(s, "lisi@qq.com") 修改值s.email 普通访问验证——反射 set 后直接访问也看到新值readField(Object obj, String fieldName) 和 setField(Object obj, String fieldName, Object value),内部就是 getField + get/setpackage course;
import java.lang.reflect.Field;
public class FieldPublicTest {
public static void main(String[] args) throws Exception {
// 1. 创建 Student 对象(六参构造)
// 2. 反射读取 email、phone 并打印
// 3. 反射修改 email、phone
// 4. 用 s.email / s.phone 普通访问验证修改生效
}
// 通用工具:读取对象 obj 的 public 字段 fieldName 的值
public static Object readField(Object obj, String fieldName) throws Exception {
// getField(fieldName) → field.get(obj)
return null;
}
// 通用工具:给对象 obj 的 public 字段 fieldName 赋值 value
public static void setField(Object obj, String fieldName, Object value) throws Exception {
// getField(fieldName) → field.set(obj, value)
}
}
输出示例:
读取 email:zhangsan@163.com
读取 phone:13636363366
反射修改后:lisi@qq.com / 13900000000
用 s.email 普通访问验证:lisi@qq.com ← 反射 set 与普通访问是同一份数据
用 s.phone 普通访问验证:13900000000
选做加分:
getFields() 遍历打印 Student 的所有 public 字段名(email、phone、school——school 是 public 也会被列出)getField("id")(private 字段)尝试获取,运行观察抛 NoSuchFieldException——验证"getField 只能拿 public"的口诀readField / setField 对 private 字段 name 调用试试,观察抛 IllegalAccessException——为练习三做铺垫思考题:
getField("字段名") 里的字段名写错(不存在),会抛什么异常?(提示:NoSuchFieldException,受检异常)field.set(s, "lisi@qq.com") 修改后,为什么 s.email 直接访问也变成新值?(提示:反射操作的就是对象的真实字段内存,同一份数据)field.get(obj) 返回的是什么类型?给 String 字段取值需要强转吗?(提示:返回 Object,使用时通常强转)难度:⭐⭐⭐⭐
知识点:clazz.getDeclaredField("字段名") 获取字段(含 private)、field.setAccessible(true) 暴力反射(临时取消访问权限检查)、field.get/set 读写、通用字段读写工具(不区分权限)
场景描述:课堂 Demo01 演示③ 对 private 字段 name 做了暴力反射——getDeclaredField 拿到 private 字段 → setAccessible(true) 放开权限 → get/set 操作(这就是反射"破坏封装性"的体现)。本练习要求:写一个不区分字段权限的通用工具 setFieldValue(obj, fieldName, value) / getFieldValue(obj, fieldName)——内部流程 getDeclaredField → setAccessible(true) → set/get,用这个工具给 Student 的 4 个 private 字段(id/name/age/gender)赋值并读取。
写通用工具,能对任意权限(public/private)的字段赋值和读取;用它对 Student 的 4 个 private 字段赋值并读取验证。
Class.forName("course.Student").getConstructor().newInstance()(复用上一课反射创建对象)setFieldValue(Object obj, String fieldName, Object value):getDeclaredField(fieldName) → setAccessible(true) → field.set(obj, value)getFieldValue(Object obj, String fieldName):同样套路 → field.get(obj)getFieldValue 读取打印验证,强转成 Student 调用 study() 观察 name 生效package course;
import java.lang.reflect.Field;
public class FieldPrivateTest {
public static void main(String[] args) throws Exception {
// 1. 反射创建 Student 空对象(Class.forName + getConstructor().newInstance())
// 2. 用 setFieldValue 给 id / name / age / gender 四个 private 字段赋值
// 3. 用 getFieldValue 读取四个字段打印验证
// 4. 强转成 Student 调用 study(),观察 name 是否生效
}
// 通用工具:给对象 obj 的任意权限字段 fieldName 赋值 value(getDeclaredField + setAccessible + set)
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
// getDeclaredField(fieldName)
// setAccessible(true)
// set(obj, value)
}
// 通用工具:读取对象 obj 的任意权限字段 fieldName 的值(getDeclaredField + setAccessible + get)
public static Object getFieldValue(Object obj, String fieldName) throws Exception {
// getDeclaredField(fieldName)
// setAccessible(true)
// get(obj)
return null;
}
}
输出示例:
id = 1001
name = 李四
age = 20
gender = 男
李四在学习..... ← 反射给 private name 赋值生效,study() 输出
选做加分:
setFieldValue 里的 setAccessible(true) 注释掉,运行观察抛 IllegalAccessException——体会"private 字段不放开权限无法操作"getField("name")(不带 Declared)尝试获取 private 字段,观察抛 NoSuchFieldException——验证"不含 Declared 只能拿 public"field.getType().getSimpleName() + field.getName(),观察每次赋值时字段的类型名和属性名思考题:
setAccessible(true) 做了什么?(提示:临时取消 Java 访问权限检查,private 也能访问)难度:⭐⭐⭐⭐⭐
知识点:静态属性反射操作(field.get(null) / field.set(null, 值)——静态成员属于整个类、不传对象)、Field 反射全家桶(getDeclaredFields + setAccessible + set)、ORM 框架"数据库数据 → Java 类字段映射"底层原理(本课类注释:"框架查询数据库,把数据库的数据一个一个映射到 Java 类中,用的就是 Field.set()")、instanceof 判断 + 强转
场景描述:这是本课最重要的知识落地——ORM 框架(MyBatis/Hibernate)的底层映射原理。框架查询数据库后,把一行数据库记录(字段名 → 值)映射成 Java 对象(属性名 → 值),靠的就是反射 Field.set()。本练习做两件事:
public static String school 赋值/读取——注意对象参数传 null(静态成员属于整个类),再用 Student.school 直接访问验证mapToObject(Map<String, Object> row, Class<?> clazz)——把模拟"数据库一行记录"的 Map 映射成任意类的对象:反射创建空对象 → 遍历该类全部字段 → 字段名在 row 中则 field.set(obj, 值)(自动包含 private,因为有 setAccessible)→ 返回映射好的对象(1)用反射读取/修改静态字段 school(对象参数传 null),验证全类共享;(2)写通用方法 mapToObject,把"数据库行"Map 映射成 Student 对象(含 private 字段),调用 study() 验证对象可用。
clazz.getDeclaredField("school") + setAccessible(true) 获取静态字段schoolField.get(null) 读取(对象参数传 null)、schoolField.set(null, "哈尔滨佛学院") 修改Student.school 直接访问验证——反射修改静态属性全类生效row.put("id", 1001)、row.put("name", "张三")、row.put("age", 25)、row.put("gender", "男")、row.put("email", "zhangsan@163.com")、row.put("phone", "13800000000")mapToObject(row, Student.class):
clazz.getConstructor().newInstance() 反射创建空对象getDeclaredFields() 遍历全部字段setAccessible(true) → field.set(obj, row.get(fieldName)) ← 映射核心getFieldValue 读取映射后的字段打印验证(Student) obj 强转 + study() 调用验证package course;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class OrmFieldMapTest {
public static void main(String[] args) throws Exception {
// ===== 第 1 部分:操作静态字段 school(对象参数传 null)=====
// 1. 获取 school 字段 + 放开访问权限
// 2. schoolField.get(null) 读取、schoolField.set(null, "哈尔滨佛学院") 修改
// 3. Student.school 直接访问验证
// ===== 第 2 部分:模拟 ORM —— 数据库行 → Java 对象 =====
// 4. 创建 Map 模拟数据库一行记录(key=字段名,value=值)
Map<String, Object> row = new HashMap<>();
// row.put("id", 1001); row.put("name", "张三"); ...
// 5. 调用通用方法,把"数据库行"映射成 Student 对象
Object obj = mapToObject(row, Student.class);
// 6. 打印映射结果 + 强转调用 study() 验证
}
// 通用映射方法:把 Map(数据库一行记录)映射成 clazz 的对象 —— ORM 底层原理
public static Object mapToObject(Map<String, Object> row, Class<?> clazz) throws Exception {
// 1. 反射创建空对象(getConstructor().newInstance())
// 2. getDeclaredFields() 遍历该类全部字段
// 3. 字段名在 row 里 → setAccessible(true) → field.set(obj, row.get(fieldName))
// 4. 返回映射好的对象
return null;
}
}
输出示例:
静态属性 school:哈尔滨佛学院 ← schoolField.get(null) 读取
修改后 Student.school:福建桥梁职业技术学院 ← 反射 set(null, ...) 后直接访问验证
映射结果:
id = 1001
name = 张三
age = 25
gender = 男
email = zhangsan@163.com
phone = 13800000000
张三在学习..... ← 映射后的对象调用 study() 正常
选做加分:
Teacher 类(含 private name、public teach() 方法),用同一个 mapToObject 传入 Teacher.class 和一个对应的 row——代码一行不改完成映射——体会"反射不写死类"的通用威力field.set(null, ...) 一次性统一赋值(所有对象共享一份)"正在映射字段:name = 张三"——直观看到 ORM 逐字段映射的过程思考题:
get/set 的对象参数为什么传 null?(提示:静态成员属于整个类、不属于任何对象——本课核心知识点)Field.set() 来映射数据库数据,而不是直接调用 setter?(提示:框架在编译期不知道实体类有哪些属性、有哪些 setter,靠反射运行时动态拿到——配置/注解驱动的解耦)mapToObject 里没有 setter、没有写死 Student 的任何一个属性名,为什么能把 private 字段都赋上值?(提示:getDeclaredFields 拿全部字段 + setAccessible(true) 放开权限)user_name、Java 属性叫 userName(下划线 vs 驼峰),mapToObject 直接按字段名匹配会失败——你会怎么解决?(提示:字段名转换——把下划线转驼峰再匹配,这就是 MyBatis 的 mapUnderscoreToCamelCase 配置的底层逻辑)难度:⭐⭐⭐
知识点:getMethods()(所有 public 方法,含父类/接口继承如 toString/hashCode)、getDeclaredMethods()(本类全部含 private,不含父类/接口)、method.getParameterTypes() 参数类型数组 + Stream 流映射、method.getReturnType() 返回值类型、method.getName() 方法名
场景描述:课堂 Demo02 演示① 遍历打印了 Student 的所有方法。本练习把"打印一个类的所有方法"封装成通用工具方法——分别打印 public 方法(含父类 Object 继承的 toString 等) 和 本类全部方法(含 private 的 read/write),输出格式:方法名(参数类型列表) 返回值=xxx。对 Student 调用,观察 getMethods() 里哪些是 Object 继承来的(toString/hashCode/equals/getClass),getDeclaredMethods() 里多出哪些私有方法(read/write)。
写两个通用方法:printPublicMethods(Class<?> clazz) 打印所有 public 方法(含继承)、printDeclaredMethods(Class<?> clazz) 打印本类全部方法(含 private),对 Student.class 调用并对比差异。
printPublicMethods(Class<?> clazz):clazz.getMethods() 遍历所有 public 方法getName() 方法名 + getParameterTypes() 参数类型数组(Arrays.stream(...) 用 Stream 流映射成 List<String> 简单名)+ getReturnType().getSimpleName() 返回值类型printDeclaredMethods(Class<?> clazz):clazz.getDeclaredMethods() 遍历本类全部方法(含 private),同样的打印格式Student.class 分别调用,对比两组输出差异getMethods() 里有多少方法是 Object 继承来的(toString/hashCode/equals/getClass/wait/notify/notifyAll)package course;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class MethodPrintUtilTest {
public static void main(String[] args) {
// 1. 打印 Student 的所有 public 方法(含父类 Object 继承的 toString 等)
// 2. 打印 Student 的本类全部方法(含 private 的 read/write)
}
// 通用工具:打印任意类 clazz 的所有 public 方法(方法名(参数类型列表) 返回值=xxx)
public static void printPublicMethods(Class<?> clazz) {
// getMethods() 遍历
// getParameterTypes() → Arrays.stream + map(getSimpleName) + collect(List)
// getName() / getReturnType().getSimpleName() 拼接打印
}
// 通用工具:打印任意类 clazz 的本类全部方法(含 private)
public static void printDeclaredMethods(Class<?> clazz) {
// getDeclaredMethods() 遍历,同样的打印格式
}
}
输出示例:
========Student 的 public 方法(含父类/接口继承)========
getId([]) 返回值=Integer
setId([Integer]) 返回值=void
getName([]) 返回值=String
setName([String]) 返回值=void
study([]) 返回值=void
toString([]) 返回值=String ← Object 继承来的
hashCode([]) 返回值=int ← Object 继承来的
equals([Object]) 返回值=boolean ← Object 继承来的
getClass([]) 返回值=Class ← Object 继承来的
...
========Student 的本类全部方法(含 private)========
setName([String]) 返回值=void
read([]) 返回值=void ← private 方法(getMethods 里看不到)
write([]) 返回值=void ← private static 方法
study([]) 返回值=void
...
选做加分:
getMethods().length vs getDeclaredMethods().length 对比(public 含继承的更多)method.getModifiers() + Modifier.toString() 打印每个方法的修饰符(public/private/static)思考题:
getMethods() 和 getDeclaredMethods() 有什么区别?(提示:含 Declared 的本类全部含 private、不含父类/接口;不含 Declared 的 public 含父类/接口继承——口诀与 Field/Constructor 一致)getMethods() 能看到 toString 而 getDeclaredMethods() 看不到?(提示:toString 是父类 Object 的方法,Declared 只拿本类声明)getParameterTypes() 返回的是什么类型?为什么要用 Stream 流转成字符串?(提示:返回 Class<?>[],直接打印是 [class java.lang.String],映射成简单名更易读)难度:⭐⭐⭐⭐
知识点:getMethod("方法名") 获取无参方法、getMethod("方法名", 参数类型...) 获取带参方法(重载必须指明参数类型,不写/写错抛 NoSuchMethodException)、method.invoke(对象) 调用无参方法、invoke(对象, 参数...) 传参调用、void 方法 invoke 返回 null、invoke 返回值强转
场景描述:课堂 Demo02 演示② 用 getMethod("getName") + invoke(s) 调用了无参方法,用 getMethod("setName", String.class) + invoke(s, "李四") 调用了带参方法。本练习把"调用方法"封装成通用工具 invokeMethod(Object obj, String methodName, Object... args)——不写死方法名,通过参数反射调用任意 public 实例方法;再用它调用 getName(无参有返回值)和 setName(带参 void)验证,并体会 void 方法 invoke 返回 null。
封装通用方法反射调用工具,用它调用 Student 的 getName()(无参有返回值)和 setName(String)(带参 void),并验证 void 方法的 invoke 返回值。
clazz.getMethod("getName") 获取无参方法 → invoke(s) 调用,返回值强转 String 打印clazz.getMethod("setName", String.class) 获取带参方法(重载必须传参数类型)→ invoke(s, "李四") 调用(void 方法不接收返回值)s.getName() 普通访问验证反射 setter 调用生效invokeMethod(Object obj, String methodName, Object... args):根据 args 动态计算参数类型数组 → getMethod → invoke(obj, args)invokeMethod(s, "setName", "王五") 的返回值——观察 void 方法返回 nullpackage course;
import java.lang.reflect.Method;
public class MethodInvokeTest {
public static void main(String[] args) throws Exception {
// 1. 创建 Student 对象(六参构造 name="张三")
// 2. 反射调用 getName() 无参方法,接收返回值强转打印
// 3. 反射调用 setName(String) 带参方法(void 不接收返回值)
// 4. 用 s.getName() 普通访问验证反射 setter 生效
// 5. 用封装好的 invokeMethod 工具再调用 setName
// 6. 打印 invokeMethod(s, "setName", "王五") 的返回值 → null(void 方法)
}
// 通用工具:反射调用对象 obj 的 public 方法 methodName,参数 args,返回方法执行结果
public static Object invokeMethod(Object obj, String methodName, Object... args) throws Exception {
// 1. 根据 args 动态计算参数类型数组(每个参数.getClass())
// 2. getMethod(methodName, 参数类型...) 获取方法
// 3. method.invoke(obj, args) 调用并返回结果
return null;
}
}
输出示例:
反射调用 getName():张三
反射调用 setName("李四") 后:李四
通用工具调用 setName("王五") 后:王五
void 方法 invoke 返回值:null
选做加分:
setName(String, Integer) 方法,用 invokeMethod 分别调用两个重载版本——体会"重载方法必须传参数类型区分"getMethod("setName")(不写参数类型)尝试获取,运行观察抛 NoSuchMethodException——验证"重载方法不指明参数类型找不到"invokeMethod(s, "setAge", "abc")(age 是 Integer,传 String),观察 invoke 时抛 IllegalArgumentException(参数类型不匹配)思考题:
invoke 的第一个参数是什么?为什么调用实例方法必须传对象?(提示:实例方法属于某个对象,必须指明"哪个对象的方法")invoke 返回什么?有返回值的方法怎么拿结果?(提示:void 返回 null;有返回值返回 Object,可强转成目标类型)getMethod 必须传参数类型?(提示:方法名相同、参数列表不同,String.class 用来区分是哪个重载版本)难度:⭐⭐⭐⭐
知识点:getDeclaredMethod("方法名") 获取私有方法、setAccessible(true) 暴力反射、invoke(对象) 调用私有实例方法、invoke(null) 调用静态方法(静态方法 invoke 传 null,与 Field 操作静态属性传 null 同一规律)、不区分权限的通用方法调用工具
场景描述:课堂 Demo02 演示③④ 调用了 Student 的私有方法 read()(实例方法)和 write()(静态方法)。本练习写不区分权限的通用工具 invokeAnyMethod(Object obj, String methodName, Object... args)——内部流程 getDeclaredMethod → setAccessible(true) → invoke,用它调用 private read()(实例方法 invoke 传对象 s)和 private static write()(静态方法 invoke 传 null)——完整体会 invoke 四种调用规律中的"实例方法传对象、静态方法传 null、私有方法先暴力反射"。
写通用工具能调用任意权限方法,用它调用 Student 的 private read()(实例方法)和 private static write()(静态方法),并做暴力反射对比实验。
clazz.getDeclaredMethod("read") 获取私有实例方法 → setAccessible(true) → invoke(s) 调用(传对象 s)clazz.getDeclaredMethod("write") 获取私有静态方法 → setAccessible(true) → invoke(null) 调用(传 null)invokeAnyMethod(Object obj, String methodName, Object... args):getDeclaredMethod + setAccessible(true) + invoke(obj, args)setAccessible(true) 注释掉,运行观察异常package course;
import java.lang.reflect.Method;
public class MethodPrivateTest {
public static void main(String[] args) throws Exception {
// 1. 创建 Student 对象,反射调用 setName("张三")(复用练习六工具)
// 2. 暴力反射调用私有实例方法 read()(invoke 传对象 s)
// 3. 暴力反射调用私有静态方法 write()(invoke 传 null)
// 4. 用封装好的 invokeAnyMethod 工具调用 read(obj 传 s)/ write(obj 传 null)
}
// 通用工具:反射调用任意权限方法(getDeclaredMethod + setAccessible + invoke)
public static Object invokeAnyMethod(Object obj, String methodName, Object... args) throws Exception {
// 1. 计算参数类型数组
// 2. getDeclaredMethod(methodName, 参数类型...) 获取方法(含 private)
// 3. setAccessible(true) 暴力反射放开权限
// 4. invoke(obj, args) 调用并返回结果
return null;
}
}
输出示例:
张三读....... ← 暴力反射调用 private read()(实例方法,invoke 传对象 s,name 已是"张三")
静态方法...写............... ← 暴力反射调用 private static write()(静态方法,invoke 传 null)
张三读....... ← 通用工具调用 read()(obj 传 s)
静态方法...写............... ← 通用工具调用 write()(obj 传 null)
选做加分:
invokeAnyMethod 里的 setAccessible(true) 注释掉,运行观察抛 IllegalAccessException——体会"私有方法不放开权限无法 invoke"getMethod("read")(不带 Declared)尝试获取私有方法,运行观察抛 NoSuchMethodException——验证"getMethod 只能拿 public"的口诀invoke(s)),观察是否也能执行——体会静态方法不依赖对象,传什么都会被忽略method.getModifiers() + Modifier.toString() 打印 read/write 的修饰符,观察 private / private static思考题:
setAccessible(true) 才能 invoke?(提示:Java 访问权限检查,不放开抛 IllegalAccessException——暴力反射)getDeclaredMethod 与 getMethod 的区别?调用私有方法为什么必须用前者?(提示:口诀"含 Declared 的能拿私有")难度:⭐⭐⭐⭐⭐
知识点:反射三件套全家桶(Constructor 创建对象 + Field 给字段赋值 + Method 调用方法)、getDeclaredMethods() 遍历扫描方法、私有方法暴力反射、InvocationTargetException(invoke 包装被调方法内部异常,getCause() 取真实异常)、method.invoke 返回值、startsWith("test") 过滤测试方法
场景描述:这是"Method 是测试框架、注解解释器的基础"的完整落地——JUnit 测试框架的底层原理:扫描一个类的所有方法,找出测试方法,反射创建对象后逐个调用。本练习给 Student 添加几个 private 测试方法(testAdd / testHello / testFail),写一个"迷你测试框架 MiniJUnitTest":
test 开头的方法getCause() 打印真实异常并统计"测试失败"——完整走一遍"反射三件套(创建对象/调方法)+ 暴力反射 + 异常处理"的框架级流程。
写一个迷你测试框架:反射创建 Student 对象,扫描所有以 test 开头的私有方法并暴力反射调用,统计测试通过/失败个数(方法抛异常算失败,用 InvocationTargetException.getCause() 打印原因)。
testAdd()(正常通过)、testHello()(正常通过)、testFail()(故意抛异常)Student.class.getConstructor().newInstance() 反射创建被测对象clazz.getDeclaredMethods() 遍历 Student 全部方法method.getName().startsWith("test") 找出测试方法(过滤掉 read/write/study/getXxx 等)setAccessible(true) + invoke(obj) 执行InvocationTargetException → getCause() 打印真实异常信息package course;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MiniJUnitTest {
public static void main(String[] args) throws Exception {
// 1. 反射创建被测 Student 对象(getConstructor().newInstance())
// 2. getDeclaredMethods() 遍历 Student 全部方法
// 3. 方法名以 "test" 开头 → 认定为测试方法
// 4. 对每个测试方法:setAccessible(true) + invoke(obj) 执行
// 5. 捕获 InvocationTargetException → getCause() 打印真实异常
// 6. 统计通过/失败个数
}
}
Student 补充测试方法(在被测类 course/Student.java 中补充):
// 来源:course/Student.java(模拟被测类的测试方法)
private void testAdd() {
System.out.println("测试:加法计算");
int a = 1 + 1;
if (a != 2) throw new RuntimeException("加法计算错误");
}
private void testHello() {
System.out.println("测试:打招呼 " + this.name);
}
private void testFail() {
throw new RuntimeException("故意失败的方法");
}
输出示例:
找到测试方法:testAdd
测试通过:testAdd
找到测试方法:testHello
测试通过:testHello
找到测试方法:testFail
测试失败:testFail,原因:故意失败的方法
测试通过 2 个,失败 1 个
选做加分:
getMethods() 扫描——观察 toString/hashCode 等 Object 继承方法也会被扫到,思考怎么过滤掉(提示:!method.getName().equals("toString") 或判断 getDeclaringClass())testAdd(int a, int b),用 invoke(obj, 3, 5) 传参调用——体会"参数如何通过 invoke 传入"boolean(true 通过 false 失败),invoke 返回 Object 强转 boolean 判断——体会"invoke 返回方法执行结果"@MyTest,扫描时用 method.isAnnotationPresent(MyTest.class) 只调用带注解的方法——这就是 JUnit 的 @Test 注解底层原理思考题:
obj.testAdd() 调用?(提示:框架在编译期不知道被测类有哪些测试方法——运行期动态扫描,这就是"为什么需要反射")invoke 抛的是 InvocationTargetException 而不是原异常?(提示:invoke 把被调方法内部异常包装了一层,需要 getCause() 取真正的异常对象)startsWith("test") 过滤——只有 test 开头的方法才会被认定为测试方法)| 知识点 | 说明 |
|---|---|
getDeclaredFields() |
返回本类(不含父类)声明的所有字段数组,包括 private——遍历可打印全部字段 |
getFields() |
返回所有 public 字段数组,包括从父类继承的——与 getDeclaredFields 的核心区别 |
getField("字段名") |
返回指定的单个 public 字段,字段名不存在抛 NoSuchFieldException |
getDeclaredField("字段名") |
返回指定的单个字段(包括 private)——操作私有字段必须用它 |
| Declared 口诀 | 含 Declared 的能拿私有(本类全部、不含父类);不含 Declared 的只能拿 public(含父类继承) |
field.get(对象) |
读取对象 obj 的该属性值,返回 Object(使用时常强转) |
field.set(对象, 值) |
设置对象 obj 的该属性值(参数:目标对象 + 要赋的值) |
field.setAccessible(true) |
暴力反射:临时取消访问权限检查——操作 private 字段必须调用,否则抛 IllegalAccessException |
field.getType() + getSimpleName() |
获取属性类型(如 Integer/String) |
field.getName() |
获取属性名 |
field.getModifiers() + Modifier.toString() |
获取修饰符 int 值并解析成可读字符串(private / public / public static...) |
| 操作静态字段传 null | 静态成员属于整个类、不属于任何对象——field.get(null) / field.set(null, 值);修改后 类名.属性名 直接访问全类生效 |
| 反射赋值与普通访问同一份数据 | field.set(obj, ...) 修改后,obj.属性名 直接访问也看到新值——反射操作的就是对象真实字段内存 |
| ORM 映射底层原理 | 框架查询数据库后,用 Field.set() 把数据库数据一个一个映射到 Java 类中(MyBatis/Hibernate 结果映射) |
| 通用映射 mapToObject | 反射创建空对象 + getDeclaredFields 遍历 + 字段名匹配 row 的 key + setAccessible(true) + field.set——模拟数据库行 → Java 对象 |
| 反射的通用性 | 工具方法传入任意 Class/对象都能工作(打印任意类字段、读写任意对象属性)——不写死具体类,配置驱动 + 解耦 |
| 反射的注意事项 | 日常业务尽量不用:破坏封装性、效率低 3~100 倍;学习反射为理解框架底层源码(ORM/IOC) |
getMethods() |
返回所有 public 方法数组,包括父类/接口继承的(如 toString/hashCode/getClass) |
getMethod(name, parameterTypes) |
返回指定的单个 public 方法;重载方法必须传参数类型区分(如 getMethod("setName", String.class)),不写/写错抛 NoSuchMethodException |
getDeclaredMethods() |
返回本类所有方法数组,包括 private,但不包括父类/接口的方法 |
getDeclaredMethod(name, parameterTypes) |
返回指定的单个方法,包括 private——操作私有方法必须用它 |
method.invoke(obj, 参数...) |
调用方法,返回方法执行结果(Object 可强转);实例方法第一参数传对象、静态方法传 null、void 方法返回 null、private 先 setAccessible(true) |
method.setAccessible(true) |
暴力反射:临时取消访问权限检查——调用 private 方法必须调用,否则抛 IllegalAccessException |
method.getReturnType() / getParameterTypes() / getModifiers() |
获取返回值类型(getSimpleName() 得简单名)/ 参数类型数组(Stream 映射简单名)/ 修饰符(Modifier.toString 解析) |
| invoke 四种调用规律 | ①实例方法 invoke(对象,参数...) 传对象 ②静态方法 invoke(null,参数...) 传 null ③void 方法 invoke 返回 null ④private 方法先 setAccessible(true) |
| 遍历打印方法 | getMethods()/getDeclaredMethods() 遍历 + getParameterTypes() + getReturnType().getSimpleName() 输出方法名/参数/返回值——对比含继承 vs 本类全部 |
| 反射调用 setter/getter | getMethod("setName", String.class) + invoke(obj, "李四") 反射调用 setter 赋值(void 不接收返回值)、getMethod("getName") + invoke(obj) 调用 getter 取返回值——框架 BeanUtils 的底层 |
| InvocationTargetException | invoke 调用方法时被调方法内部抛异常,invoke 会把它包装成 InvocationTargetException 抛出——需要 getCause() 获取真正的异常 |
| 模拟测试框架(JUnit 原理) | getDeclaredMethods() 扫描类的方法 → startsWith("test") 过滤测试方法 → setAccessible(true) + invoke(obj) 执行 → 捕获 InvocationTargetException 统计通过/失败——JUnit 扫描 @Test 注解方法的底层套路 |
| 反射三件套组合 | Constructor(newInstance 创建对象)+ Field(get/set 读写字段)+ Method(invoke 调用方法)——三者组合 = MyBatis 结果映射 / Spring Bean 初始化 / 测试框架执行测试用例的完整底层链路 |
| 反射 API 的受检异常 | NoSuchFieldException(字段名不存在)/ NoSuchMethodException(方法名或参数类型不匹配)/ IllegalAccessException(private 未放开权限)/ InvocationTargetException(被调方法内部抛异常)/ ClassNotFoundException(类名写错)——必须处理(main 用 throws) |
| 反射 API 的受检异常 | NoSuchFieldException(字段名不存在)/ IllegalAccessException(private 未放开权限)——必须处理(main 用 throws) |