Procházet zdrojové kódy

feat: 追加20260813反射Field字段练习的Method方法反射练习——练习五通用工具遍历打印任意类的所有方法(getMethods/getDeclaredMethods+getParameterTypes+getReturnType+Stream映射简单名+对比含继承vs本类全部含private)、练习六反射调用实例方法(getMethod+invoke无参/带参/重载必须传参数类型区分+void方法invoke返回null+封装invokeMethod通用工具)、练习七暴力反射调用私有方法与静态方法(getDeclaredMethod+setAccessible(true)+invoke实例方法传对象/静态方法传null+封装invokeAnyMethod通用工具)、练习八综合实战模拟测试框架JUnit简化版(getDeclaredMethods扫描test开头方法+setAccessible(true)+invoke调用+InvocationTargetException.getCause()处理真实异常+统计通过失败+反射三件套Constructor/Field/Method组合框架级流程),同步更新文档标题/说明与涵盖知识点总览,更新README课堂练习题表

WanJL před 1 týdnem
rodič
revize
35b22e9979
2 změnil soubory, kde provedl 322 přidání a 3 odebrání
  1. 1 1
      README.md
  2. 321 2
      课堂练习题/20260813-反射Field字段练习.md

+ 1 - 1
README.md

@@ -104,4 +104,4 @@
 | 2026-08-10 | [线程操作与生命周期练习](./课堂练习题/20260810-线程操作与生命周期练习.md) | 线程生命周期(getState 6 种状态)、线程优先级(setPriority)、守护线程(setDaemon/JVM 退出机制)、线程协作(join/interrupt/yield) |
 | 2026-08-11 | [volatile 与 Lock 锁练习](./课堂练习题/20260811-volatile与Lock锁练习.md) | volatile 可见性(程序永不退出)、volatile 不保证原子性 vs synchronized、Lock 锁基本用法(lock/unlock + finally)、Lock 进阶(tryLock / tryLock(timeout) / lockInterruptibly)、Lock 改造卖票问题(synchronized vs Lock 七维对比) |
 | 2026-08-12 | [反射练习](./课堂练习题/20260812-反射练习.md) | 反射遍历 SysUser 四个构造方法创建对象(public 无参/private 二参/public 六参/private 四参:Class.forName + getConstructor/getDeclaredConstructor + setAccessible(true) 暴力反射 + newInstance + instanceof 强转)、反射给私有属性赋值(getDeclaredField + setAccessible + field.set/get)、反射调用方法(getMethod + invoke 带参返回值)、综合实战配置文件驱动 + 反射(模拟 Spring IOC:Properties 加载类名/属性/方法名) |
-| 2026-08-13 | [反射 Field 字段练习](./课堂练习题/20260813-反射Field字段练习.md) | 反射遍历打印任意类的所有字段(getDeclaredFields + getModifiers + Modifier.toString + getType + getName,对 Student.class/String.class 通用工具)、反射读取/修改 public 字段(getField + get/set + 封装 readField/setField 通用方法)、暴力反射读取/修改 private 字段(getDeclaredField + setAccessible(true) + get/set 通用工具 setFieldValue/getFieldValue)、操作静态字段(get/set 对象参数传 null)+ 模拟 ORM 数据库行 → Java 对象映射(mapToObject 通用方法:反射创建空对象 + getDeclaredFields 遍历 + 字段名匹配 row + field.set 逐字段映射,ORM 底层原理) |
+| 2026-08-13 | [反射 Field 字段反射与 Method 方法反射练习](./课堂练习题/20260813-反射Field字段练习.md) | 反射遍历打印任意类的所有字段(getDeclaredFields + getModifiers + Modifier.toString + getType + getName,对 Student.class/String.class 通用工具)、反射读取/修改 public 字段(getField + get/set + 封装 readField/setField 通用方法)、暴力反射读取/修改 private 字段(getDeclaredField + setAccessible(true) + get/set 通用工具 setFieldValue/getFieldValue)、操作静态字段(get/set 对象参数传 null)+ 模拟 ORM 数据库行 → Java 对象映射(mapToObject 通用方法:反射创建空对象 + getDeclaredFields 遍历 + 字段名匹配 row + field.set 逐字段映射,ORM 底层原理)、反射遍历打印任意类的所有方法(getMethods/getDeclaredMethods + getParameterTypes + getReturnType + Stream 映射简单名)、反射调用实例方法(getMethod + invoke 无参/带参/重载必须传参数类型 + void 返回 null + 封装 invokeMethod 通用工具)、暴力反射调用私有方法与静态方法(getDeclaredMethod + setAccessible(true) + invoke 实例传对象/静态传 null + 封装 invokeAnyMethod 通用工具)、模拟测试框架 JUnit 简化版(getDeclaredMethods 扫描 test 开头方法 + setAccessible(true) + invoke + InvocationTargetException.getCause() 处理 + 反射三件套综合实战) |

+ 321 - 2
课堂练习题/20260813-反射Field字段练习.md

@@ -1,6 +1,6 @@
-# 2026-08-13 反射 Field 字段反射练习(课堂练习用)
+# 2026-08-13 反射 Field 字段反射与 Method 方法反射练习(课堂练习用)
 
-> **说明**:练习今日新学知识点——**反射 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 类的底层原理)。练习一写通用工具遍历打印任意类的全部字段;练习二读取/修改 public 字段;练习三暴力反射读取/修改 private 字段;练习四操作静态字段 + 模拟 ORM 框架把"数据库一行记录"映射成 Java 对象(综合实战)
+> **说明**:练习今日新学知识点——**反射 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 方法反射——通用工具遍历打印全部方法;反射调用实例方法(无参/带参/重载);暴力反射调用私有方法与静态方法;模拟测试框架反射三件套综合实战
 
 ---
 
@@ -332,6 +332,311 @@ phone = 13800000000
 
 ---
 
+## 练习五:通用工具 —— 遍历打印任意类的所有方法
+
+**难度**:⭐⭐⭐
+**知识点**:`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 调用并对比差异。
+
+### 实现步骤
+
+1. 定义 `printPublicMethods(Class<?> clazz)`:`clazz.getMethods()` 遍历所有 public 方法
+2. 每个方法:`getName()` 方法名 + `getParameterTypes()` 参数类型数组(`Arrays.stream(...)` 用 Stream 流映射成 `List<String>` 简单名)+ `getReturnType().getSimpleName()` 返回值类型
+3. 定义 `printDeclaredMethods(Class<?> clazz)`:`clazz.getDeclaredMethods()` 遍历本类全部方法(含 private),同样的打印格式
+4. main 里用 `Student.class` 分别调用,对比两组输出差异
+5. 数一数 `getMethods()` 里有多少方法是 Object 继承来的(toString/hashCode/equals/getClass/wait/notify/notifyAll)
+
+### 代码框架
+
+```java
+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)
+- 统计 public 方法里哪些是 Object 继承来的——手动挑出 toString/hashCode/equals/getClass/wait/notify/notifyAll,数一数有几个
+
+**思考题**:
+1. `getMethods()` 和 `getDeclaredMethods()` 有什么区别?(提示:含 Declared 的本类全部含 private、不含父类/接口;不含 Declared 的 public 含父类/接口继承——口诀与 Field/Constructor 一致)
+2. 为什么 `getMethods()` 能看到 `toString` 而 `getDeclaredMethods()` 看不到?(提示:toString 是父类 Object 的方法,Declared 只拿本类声明)
+3. `getParameterTypes()` 返回的是什么类型?为什么要用 Stream 流转成字符串?(提示:返回 `Class<?>[]`,直接打印是 `[class java.lang.String]`,映射成简单名更易读)
+
+---
+
+## 练习六:反射调用实例方法 —— getMethod + invoke(无参/带参/重载)
+
+**难度**:⭐⭐⭐⭐
+**知识点**:`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 返回值。
+
+### 实现步骤
+
+1. 创建 Student 对象(六参构造,name="张三")
+2. `clazz.getMethod("getName")` 获取无参方法 → `invoke(s)` 调用,**返回值强转 String** 打印
+3. `clazz.getMethod("setName", String.class)` 获取带参方法(**重载必须传参数类型**)→ `invoke(s, "李四")` 调用(**void 方法不接收返回值**)
+4. 用 `s.getName()` 普通访问验证反射 setter 调用生效
+5. 封装通用方法 `invokeMethod(Object obj, String methodName, Object... args)`:根据 args 动态计算参数类型数组 → `getMethod` → `invoke(obj, args)`
+6. 打印 `invokeMethod(s, "setName", "王五")` 的返回值——观察 **void 方法返回 null**
+
+### 代码框架
+
+```java
+package 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
+```
+
+**选做加分**:
+- **重载实验**:给 Student 重载一个 `setName(String, Integer)` 方法,用 invokeMethod 分别调用两个重载版本——体会"重载方法必须传参数类型区分"
+- 用 `getMethod("setName")`(不写参数类型)尝试获取,运行观察抛 **NoSuchMethodException**——验证"重载方法不指明参数类型找不到"
+- **参数类型不匹配实验**:调用 `invokeMethod(s, "setAge", "abc")`(age 是 Integer,传 String),观察 invoke 时抛 **IllegalArgumentException**(参数类型不匹配)
+
+**思考题**:
+1. `invoke` 的第一个参数是什么?为什么调用实例方法必须传对象?(提示:实例方法属于某个对象,必须指明"哪个对象的方法")
+2. **void 方法**调用 `invoke` 返回什么?有返回值的方法怎么拿结果?(提示:void 返回 null;有返回值返回 Object,可强转成目标类型)
+3. **重载方法**为什么 `getMethod` 必须传参数类型?(提示:方法名相同、参数列表不同,`String.class` 用来区分是哪个重载版本)
+
+---
+
+## 练习七:暴力反射调用私有方法与静态方法 —— getDeclaredMethod + setAccessible(true) + invoke(null)
+
+**难度**:⭐⭐⭐⭐
+**知识点**:`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()(静态方法),并做暴力反射对比实验。
+
+### 实现步骤
+
+1. 创建 Student 对象,反射调用 setName("张三")(复用练习六的工具)
+2. `clazz.getDeclaredMethod("read")` 获取私有实例方法 → `setAccessible(true)` → `invoke(s)` 调用(**传对象 s**)
+3. `clazz.getDeclaredMethod("write")` 获取私有静态方法 → `setAccessible(true)` → `invoke(null)` 调用(**传 null**)
+4. 封装通用工具 `invokeAnyMethod(Object obj, String methodName, Object... args)`:`getDeclaredMethod` + `setAccessible(true)` + `invoke(obj, args)`
+5. 用工具调用 read(obj 传 s)、write(obj 传 null)验证
+6. **对比实验**:把工具里的 `setAccessible(true)` 注释掉,运行观察异常
+
+### 代码框架
+
+```java
+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"的口诀
+- **静态方法传对象实验**:write() 是静态方法,invoke 第一参数传对象 s 试试(`invoke(s)`),观察是否也能执行——体会静态方法不依赖对象,传什么都会被忽略
+- 用 `method.getModifiers()` + `Modifier.toString()` 打印 read/write 的修饰符,观察 `private` / `private static`
+
+**思考题**:
+1. 为什么私有方法必须 `setAccessible(true)` 才能 invoke?(提示:Java 访问权限检查,不放开抛 IllegalAccessException——暴力反射)
+2. 静态方法 invoke 为什么传 **null**?与 Field 操作静态属性传 null 是什么关系?(提示:静态成员属于整个类、不属于任何对象——本课核心规律统一)
+3. `getDeclaredMethod` 与 `getMethod` 的区别?调用私有方法为什么必须用前者?(提示:口诀"含 Declared 的能拿私有")
+
+---
+
+## 练习八:综合实战 —— 模拟测试框架(JUnit 简化版):反射三件套扫描并调用私有测试方法
+
+**难度**:⭐⭐⭐⭐⭐
+**知识点**:反射三件套全家桶(Constructor 创建对象 + Field 给字段赋值 + Method 调用方法)、`getDeclaredMethods()` 遍历扫描方法、私有方法暴力反射、**`InvocationTargetException`**(invoke 包装被调方法内部异常,`getCause()` 取真实异常)、`method.invoke` 返回值、`startsWith("test")` 过滤测试方法
+
+**场景描述**:这是"Method 是测试框架、注解解释器的基础"的完整落地——**JUnit 测试框架的底层原理**:扫描一个类的所有方法,找出测试方法,反射创建对象后逐个调用。本练习给 Student 添加几个 **private 测试方法**(testAdd / testHello / testFail),写一个"**迷你测试框架** `MiniJUnitTest`":
+1. **反射创建被测对象**(Constructor)
+2. **getDeclaredMethods() 扫描**所有方法,找出名字以 `test` 开头的方法
+3. 每个测试方法 **setAccessible(true) + invoke(对象)** 执行(Method)
+4. 方法内部抛异常 → invoke 抛 **InvocationTargetException**,`getCause()` 打印真实异常并统计"测试失败"
+
+——完整走一遍"反射三件套(创建对象/调方法)+ 暴力反射 + 异常处理"的**框架级流程**。
+
+### 案例需求
+
+写一个迷你测试框架:反射创建 Student 对象,扫描所有以 test 开头的私有方法并暴力反射调用,统计测试通过/失败个数(方法抛异常算失败,用 InvocationTargetException.getCause() 打印原因)。
+
+### 实现步骤
+
+1. 给 Student 增加 3 个 private 测试方法:`testAdd()`(正常通过)、`testHello()`(正常通过)、`testFail()`(故意抛异常)
+2. `Student.class.getConstructor().newInstance()` 反射创建被测对象
+3. `clazz.getDeclaredMethods()` 遍历 Student 全部方法
+4. 用 `method.getName().startsWith("test")` 找出测试方法(过滤掉 read/write/study/getXxx 等)
+5. 对每个测试方法:`setAccessible(true)` + `invoke(obj)` 执行
+6. **try-catch 捕获 `InvocationTargetException`** → `getCause()` 打印真实异常信息
+7. 统计"测试通过 / 测试失败"个数
+
+### 代码框架
+
+```java
+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 中补充):
+
+```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() 扫描对比**:把测试方法改成 public、改用 `getMethods()` 扫描——观察 toString/hashCode 等 Object 继承方法也会被扫到,思考怎么过滤掉(提示:`!method.getName().equals("toString")` 或判断 `getDeclaringClass()`)
+- **给测试方法传参**:写一个带参数的测试方法 `testAdd(int a, int b)`,用 `invoke(obj, 3, 5)` 传参调用——体会"参数如何通过 invoke 传入"
+- **返回值处理**:让 test 方法返回 `boolean`(true 通过 false 失败),`invoke` 返回 Object 强转 boolean 判断——体会"invoke 返回方法执行结果"
+- **注解模拟(进阶)**:定义自定义注解 `@MyTest`,扫描时用 `method.isAnnotationPresent(MyTest.class)` 只调用带注解的方法——这就是 JUnit 的 `@Test` 注解底层原理
+
+**思考题**:
+1. 测试框架为什么要用**反射扫描方法**,而不是直接 `obj.testAdd()` 调用?(提示:框架在编译期不知道被测类有哪些测试方法——运行期动态扫描,这就是"为什么需要反射")
+2. 方法内部抛异常时,为什么 `invoke` 抛的是 **InvocationTargetException** 而不是原异常?(提示:invoke 把被调方法内部异常**包装**了一层,需要 `getCause()` 取真正的异常对象)
+3. 这个"迷你测试框架"和真实的 JUnit 有什么相似之处?(提示:JUnit 也是扫描类的方法 + 反射创建对象 + 反射调用;JUnit 用 @Test 注解标记测试方法、本练习用 test 前缀过滤)
+4. 如果把测试方法改名成非 test 开头(如 study),扫描时会被执行吗?为什么?(提示:`startsWith("test")` 过滤——只有 test 开头的方法才会被认定为测试方法)
+
+---
+
 # 涵盖知识点总览
 
 | 知识点 | 说明 |
@@ -353,4 +658,18 @@ phone = 13800000000
 | **通用映射 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) |