Ver código fonte

feat: 新增20260814课堂笔记及授课代码分析——反射 Method 进阶应用(暴力反射调用私有方法 + 模拟 JUnit 单元测试框架),更新README笔记表与代码表

WanJL 1 semana atrás
pai
commit
a745661a1f
2 arquivos alterados com 670 adições e 0 exclusões
  1. 2 0
      README.md
  2. 668 0
      课堂笔记/01-JavaSE基础-笔记/20260814-笔记.md

+ 2 - 0
README.md

@@ -47,6 +47,7 @@
 | 2026-08-11 | [volatile、Lock 锁与线程池](./课堂笔记/01-JavaSE基础-笔记/20260811-笔记.md) |
 | 2026-08-12 | [类加载机制与反射](./课堂笔记/01-JavaSE基础-笔记/20260812-笔记.md) |
 | 2026-08-13 | [反射 Field 与 Method 字段/方法反射](./课堂笔记/01-JavaSE基础-笔记/20260813-笔记.md) |
+| 2026-08-14 | [Method 暴力反射调用私有方法与模拟 JUnit 单元测试框架](./课堂笔记/01-JavaSE基础-笔记/20260814-笔记.md) |
 
 ## 二、授课代码
 
@@ -79,6 +80,7 @@
 | 2026-08-11 | volatile、Lock 锁与线程池 | [`c260811`](./授课代码/c260811/src/) |
 | 2026-08-12 | 类加载机制与反射 | [`c260812`](./授课代码/c260812/src/) |
 | 2026-08-13 | 反射 Field 与 Method 字段/方法反射 | [`c260813`](./授课代码/c260813/src/) |
+| 2026-08-14 | Method 暴力反射调用私有方法与模拟 JUnit 单元测试框架 | [`c260814`](./授课代码/c260814/src/homework0813/) |
 
 ## 三、课堂练习题
 

+ 668 - 0
课堂笔记/01-JavaSE基础-笔记/20260814-笔记.md

@@ -0,0 +1,668 @@
+# 20260814 课堂笔记 — 反射 Method 进阶:暴力反射调用私有方法与模拟 JUnit 单元测试框架
+
+- **日期**:2026-08-14
+- **项目**:`c260814`
+- **包路径**:`homework0813`(默认包根目录下的具名包)
+- **作者**:WanJL
+- **内容概览**:0813 作业进阶讲解——Student 实体类(回顾 4 种字段权限 + 4 个构造方法 + 私有实例方法 read / 私有静态方法 write + 新增 3 个 `test` 前缀测试方法 testAdd/testHello/testFail)+ MethodPrivateTest(**暴力反射调用私有实例方法 read——invoke 传对象、暴力反射调用私有静态方法 write——invoke 传 null**、封装通用调用工具 `invokeAnyMethod`——用 `instanceof Class<?>` 判断"传的是 Class 则调静态方法、传的是对象则调实例方法"+ `args[i].getClass()` **动态计算参数类型数组**)+ MiniJUnitTest(**模拟 JUnit 单元测试框架**——`getConstructor().newInstance()` 反射创建被测对象 → `getDeclaredMethods()` 遍历全部方法 → 按 `test` 方法名前缀识别测试方法 → `setAccessible(true)` + `invoke()` 执行 → 捕获 `InvocationTargetException` 统计通过/失败个数,底层原理:注解识别 + 反射调用)
+
+---
+
+## 目录
+
+1. [项目结构概述:0813 作业进阶——Method 反射的综合应用](#1-项目结构概述0813-作业进阶method-反射的综合应用)
+2. [Student 实体类解析:反射目标回顾 + 新增测试方法](#2-student-实体类解析反射目标回顾--新增测试方法)
+3. [MethodPrivateTest 演示:暴力反射调用私有方法(实例 + 静态)](#3-methodprivatetest-演示暴力反射调用私有方法实例--静态)
+4. [暴力反射调用私有实例方法 read():invoke 传对象](#4-暴力反射调用私有实例方法-readinvoke-传对象)
+5. [暴力反射调用私有静态方法 write():invoke 传 null](#5-暴力反射调用私有静态方法-writeinvoke-传-null)
+6. [通用工具 invokeAnyMethod:instanceof 判断静态/实例 + 动态计算参数类型](#6-通用工具-invokeanymethodinstanceof-判断静态实例--动态计算参数类型)
+7. [MiniJUnitTest 演示:模拟 JUnit 单元测试框架(反射终极应用)](#7-minijunittest-演示模拟-junit-单元测试框架反射终极应用)
+8. [反射创建被测对象:getConstructor().newInstance()](#8-反射创建被测对象getconstructornewinstance)
+9. [getDeclaredMethods() 遍历 + test 前缀识别测试方法](#9-getdeclaredmethods-遍历--test-前缀识别测试方法)
+10. [setAccessible(true) + invoke 执行测试方法](#10-setaccessibletrue--invoke-执行测试方法)
+11. [InvocationTargetException 包装异常:getCause() 获取真实异常](#11-invocationtargetexception-包装异常getcause-获取真实异常)
+12. [通过/失败统计:模拟测试报告输出](#12-通过失败统计模拟测试报告输出)
+13. [知识点全景总结](#13-知识点全景总结)
+14. [随堂练习要点](#14-随堂练习要点)
+15. [拓展阅读](#15-拓展阅读)
+
+---
+
+## 1. 项目结构概述:0813 作业进阶——Method 反射的综合应用
+
+### 概念 —— 一个包内三个类,把上一课的 Method 反射学以致用
+
+`c260814` 项目把上一课(08-13 反射 Method)的知识点**落到两个实际场景**:`MethodPrivateTest` 演示**暴力反射调用私有方法**(并把调用过程封装成通用工具),`MiniJUnitTest` 演示**用反射模拟 JUnit 单元测试框架**(遍历方法 → 识别测试方法 → 反射执行 → 统计结果)。`Student` 实体类既是两个演示的反射目标,又新增了 3 个 `test` 前缀方法专门供 MiniJUnit 扫描执行。
+
+```text
+c260814/
+└── src/
+    └── homework0813/                  ← 包名 homework0813(0813 作业的进阶应用)
+        ├── Student.java               ← 反射目标实体类(字段/构造/私有方法 + 测试方法)
+        ├── MethodPrivateTest.java     ← 暴力反射调用私有方法 + 通用调用工具 invokeAnyMethod
+        └── MiniJUnitTest.java         ← 模拟 JUnit 单元测试框架(测试方法扫描/执行/统计)
+```
+
+> **要点**:
+> - **项目类型是"每日默认包 + 具名包"**:包名为 `homework0813`——表示这是 08-13 课后作业的进阶练习,三个类在同一个包下,相互可以直接引用(无需 import 同包类)
+> - **三个类各司其职**:Student 提供"被测对象与测试方法",MethodPrivateTest 演示"私有方法怎么反射调用",MiniJUnitTest 演示"测试框架怎么用反射批量执行测试"
+> - **本课是反射 Method 的收尾应用**:上一课学了 API(getDeclaredMethod/invoke/setAccessible),本课把 API 组合成"能干活"的工具和框架——**反射的价值在于组合应用**
+
+---
+
+## 2. Student 实体类解析:反射目标回顾 + 新增测试方法
+
+### 概念 —— 一个同时满足"私有方法反射"与"测试框架反射"的实体类
+
+`Student` 类是三个演示的共用反射目标,字段/构造方法与上一课(08-13)基本一致(private/public/public static 三种权限字段 + 4 个构造方法 + 私有实例方法 read / 私有静态方法 write + 公开方法 study),**新增了 3 个 `test` 开头的方法**——它们是 MiniJUnit 框架扫描并执行的"模拟测试用例"。
+
+```java
+// 来源:homework0813/Student.java
+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;
+
+    public Student() { }                                    // public 无参构造(MiniJUnit 反射创建对象用)
+
+    private Student(String name, Integer id) { }            // private 二参构造(上一课暴力反射目标)
+
+    public Student(Integer id, String name, Integer age, String gender) { }  // public 四参构造
+
+    public Student(Integer id, String name, Integer age, String gender, String email, String phone) { }  // public 六参构造
+
+    // …… 7 组 getter/setter(id/name/age/gender/email/phone 实例 + school 静态)……
+
+    private void read(){                                    // private 实例方法:输出 name+"读......."
+        System.out.println(this.name+"读.......");
+    }
+
+    private static void write(){                            // private 静态方法:输出"静态方法...写..............."
+        System.out.println("静态方法...写...............");
+    }
+
+    public void study(){                                    // public 实例方法:输出 name+"在学习....."
+        System.out.println(name+"在学习.....");
+    }
+
+    // 新增 3 个 test 前缀方法:MiniJUnit 扫描识别的"模拟测试用例"
+    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()` | private | 输出"测试:加法计算",计算 `1+1`,若不等于 2 抛异常 | **通过**(`1+1==2`,不抛异常) |
+| `testHello()` | private | 输出"测试:打招呼 " + name | **通过**(只是打印,不抛异常) |
+| `testFail()` | private | 直接 `throw new RuntimeException("故意失败的方法")` | **失败**(抛异常 → invoke 包装成 InvocationTargetException) |
+
+### 字段/方法权限设计对照表(回顾上一课)
+
+| 成员 | 权限 | 作用 / 对应演示 |
+|------|------|----------------|
+| `private Integer id` | private | 私有字段——Field 反射 `getDeclaredFields()` 能拿到 |
+| `private String name` | private | 私有字段——暴力反射 Field 读取/修改目标;testHello 打印 name |
+| `private Integer age` | private | 私有字段 |
+| `private String gender` | private | 私有字段 |
+| `public String email` | public | 公有字段——`getField` + `get/set` 读取/修改 |
+| `public String phone` | public | 公有字段 |
+| `public static String school` | **public + static** | 静态字段——`get(null)` / `set(null, 值)` |
+| `read()` | **private 实例方法** | 暴力反射 Method 调用目标——`getDeclaredMethod("read")` + `setAccessible(true)` + `invoke(对象)` |
+| `write()` | **private 静态方法** | 暴力反射 Method 调用目标——`getDeclaredMethod("write")` + `setAccessible(true)` + `invoke(null)` |
+| `study()` | public 实例方法 | 公开方法——普通/反射均可调用 |
+| `testAdd/testHello/testFail` | **private 测试方法** | MiniJUnit 扫描执行目标——`getDeclaredMethods()` 遍历 + test 前缀识别 |
+
+> **要点**:
+> - **私有方法不写 getter/setter**:`read()`/`write()`/`testXxx` 都是 private 且无公开入口,**普通代码无法调用**——只有反射(暴力反射)能调用它们,这正是本课"为什么需要反射"的典型场景
+> - **测试方法刻意设计"两成一败"**:`testAdd` 通过、`testHello` 通过、`testFail` 故意抛异常——这样 MiniJUnit 统计结果时能演示"成功 2 个、失败 1 个"
+> - **`testFail` 抛的是 RuntimeException**:被调方法内部抛异常时,反射 invoke 不会直接抛 RuntimeException,而是**包装成 InvocationTargetException 抛出**——这是本课重点(见第 11 节)
+
+---
+
+## 3. MethodPrivateTest 演示:暴力反射调用私有方法(实例 + 静态)
+
+### 概念 —— 一个 main 方法三个任务,私有方法反射的"全家福"
+
+`MethodPrivateTest` 的 main 方法演示了暴力反射调用私有方法的**三种完整路径**:调用私有**实例**方法(invoke 传对象)、调用私有**静态**方法(invoke 传 null)、以及用**封装好的通用工具**统一调用(自动判断实例/静态)。
+
+```java
+// 来源:homework0813/MethodPrivateTest.java
+public class MethodPrivateTest {
+    public static void main(String[] args) throws Exception {
+        // 1. 创建 Student 对象,反射调用 setName("张三")(复用练习六工具)
+        Student student = new Student(101, "张三", 25, "男", "zhangsan@qq.com", "13636363366");
+        // 2. 暴力反射调用私有实例方法 read()(invoke 传对象 s)
+        // 3. 暴力反射调用私有静态方法 write()(invoke 传 null)
+        // 4. 用封装好的 invokeAnyMethod 工具调用 read(obj 传 s)/ write(obj 传 null)
+        invokeAnyMethod(student, "read");
+        invokeAnyMethod(student.getClass(), "write");
+    }
+    // …… invokeAnyMethod 通用工具见第 6 节 ……
+}
+```
+
+### main 方法执行流程
+
+```
+new Student(101, "张三", 25, "男", "zhangsan@qq.com", "13636363366")
+      ↓
+invokeAnyMethod(student, "read")        ← obj 传 Student 对象 → 调用私有实例方法 read(),输出"张三读......."
+      ↓
+invokeAnyMethod(student.getClass(), "write")   ← obj 传 Class 对象 → 调用私有静态方法 write(),输出"静态方法...写..............."
+```
+
+| 代码 | 作用 | 说明 |
+|------|------|------|
+| `new Student(101, "张三", ...)` | 创建 Student 对象 | 六参构造(上一课已学),name 赋值为"张三" |
+| `invokeAnyMethod(student, "read")` | 调私有**实例**方法 read | obj 传的是 **Student 对象** → 通用工具判定为实例方法,invoke 第一参数传对象 |
+| `invokeAnyMethod(student.getClass(), "write")` | 调私有**静态**方法 write | obj 传的是 **Class 对象** → 通用工具判定为静态方法,invoke 第一参数传 null |
+
+> **要点**:
+> - **main 方法没有直接写 getDeclaredMethod 三件套**——注释 2、3 是把"标准写法"留作对照,实际执行走第 4 步的**封装工具**:同样能调私有方法,但代码更简洁(一行调一个)
+> - **instanceof 判断的关键区别**:同一个工具方法,**传对象 → 实例方法,传 Class → 静态方法**——用 `student.getClass()` 传 Class 对象正是"主动告知工具:我要调静态方法"
+> - **main throws Exception**:`throws Exception` 一网打尽所有受检异常(NoSuchMethodException / IllegalAccessException / InvocationTargetException)——演示代码的偷懒写法,实际开发应精确处理
+
+---
+
+## 4. 暴力反射调用私有实例方法 read():invoke 传对象
+
+### 概念 —— 标准写法:getDeclaredMethod + setAccessible(true) + invoke(对象)
+
+注释 2 要求用**标准三件套**暴力反射调用私有实例方法 `read()`——`read()` 是 private 实例方法,必须 `getDeclaredMethod("read")` 拿到方法对象(getMethod 拿不到 private)→ `setAccessible(true)` 放开权限 → `invoke(对象)` 传调用该方法的对象:
+
+```java
+// 来源:homework0813/MethodPrivateTest.java(注释 2 的标准写法,main 实际用封装工具等价实现)
+// 2. 暴力反射调用私有实例方法 read()(invoke 传对象 s)
+Class<Student> clazz = Student.class;
+Method readMethod = clazz.getDeclaredMethod("read");   // ① getDeclaredMethod 拿到 private 方法
+readMethod.setAccessible(true);                        // ② 暴力反射:临时取消访问权限检查
+Object result = readMethod.invoke(student);            // ③ invoke 第一参数传"调用该方法的对象" student
+```
+
+### 三件套拆解
+
+| 步骤 | 代码 | 作用 | 说明 |
+|------|------|------|------|
+| ① | `clazz.getDeclaredMethod("read")` | 获取私有方法对象 | `read()` 无参,`getDeclaredMethod("read")` 即可;**getMethod 拿不到 private**,必须用 Declared 系列 |
+| ② | `readMethod.setAccessible(true)` | 暴力反射:临时取消访问权限检查 | 不调用则 invoke 私有方法抛 `IllegalAccessException` |
+| ③ | `readMethod.invoke(student)` | 调用私有实例方法 | **第一参数传对象 student**——实例方法属于对象,必须指明"调用哪个对象的方法";read() 内部用 this.name,输出"张三读......." |
+
+> **要点**:
+> - **私有实例方法三件套**:`getDeclaredMethod`(拿 private)+ `setAccessible(true)`(放开权限)+ `invoke(对象)`(传调用对象)——与 Field/Constructor 的暴力反射套路完全一致
+> - **invoke 第一参数 = 调用方法的对象**:read() 是实例方法,内部用了 `this.name`,所以必须传 student 对象;**传错对象或传 null 会抛 IllegalArgumentException 或 NullPointerException**
+> - **调用私有方法 = 破坏封装**:read() 本来没有任何公开入口,反射硬生生调用成功了——这正是"反射破坏封装性"的体现,也是测试框架能测私有方法的原理
+
+---
+
+## 5. 暴力反射调用私有静态方法 write():invoke 传 null
+
+### 概念 —— 静态方法不属于对象,invoke 第一参数传 null
+
+注释 3 要求暴力反射调用私有**静态**方法 `write()`——静态方法属于整个类、不属于任何对象,所以 `invoke` 第一参数**不传对象、传 null**(与 Field 操作静态属性传 null 规律统一):
+
+```java
+// 来源:homework0813/MethodPrivateTest.java(注释 3 的标准写法)
+// 3. 暴力反射调用私有静态方法 write()(invoke 传 null)
+Method writeMethod = clazz.getDeclaredMethod("write");    // ① getDeclaredMethod 拿到 private 静态方法
+writeMethod.setAccessible(true);                          // ② 暴力反射:临时取消访问权限检查
+Object result = writeMethod.invoke(null);                 // ③ 静态方法 invoke 第一参数传 null
+```
+
+### 私有实例方法 vs 私有静态方法对比
+
+| 对比 | 私有实例方法 read() | 私有静态方法 write() |
+|------|---------------------|----------------------|
+| 权限 | private | private |
+| 是否 static | 否(实例方法) | 是(静态方法) |
+| 获取方式 | `getDeclaredMethod("read")` | `getDeclaredMethod("write")` |
+| 是否要 setAccessible(true) | **要**(private 必须暴力反射) | **要**(private 必须暴力反射) |
+| invoke 第一参数 | **传对象** `invoke(student)` | **传 null** `invoke(null)` |
+| 输出 | `张三读.......` | `静态方法...写...............` |
+
+> **要点**:
+> - **invoke 第一参数规律(再次强调)**:**实例方法传对象、静态方法传 null**——与 Field 操作静态属性传 null 是同一规律:静态成员属于整个类,不属于任何对象
+> - **private 和 static 是两个独立维度**:write() 既 private 又 static——**private 决定了必须 getDeclaredMethod + setAccessible(true),static 决定了 invoke 传 null**,两件事都要做
+> - **write() 内部不依赖任何对象状态**:它只打印固定字符串,所以 invoke(null) 也能正常执行——如果静态方法内部访问了实例字段反而会出错(静态上下文不能直接访问实例成员)
+
+---
+
+## 6. 通用工具 invokeAnyMethod:instanceof 判断静态/实例 + 动态计算参数类型
+
+### 概念 —— 把暴力反射封装成"一行调用任意方法"的通用工具
+
+第 4、5 节的标准写法每次都要写三件套(getDeclaredMethod + setAccessible + invoke),非常啰嗦。`invokeAnyMethod` 把整个过程**封装成一个通用工具**:只要传入"目标对象(或 Class)+ 方法名 + 参数",它自动完成"计算参数类型 → 获取方法(含 private)→ 放开权限 → 调用"四步。**核心技巧有两个:`instanceof Class<?>` 判断是静态还是实例方法、`args[i].getClass()` 动态计算参数类型数组**。
+
+```java
+// 来源:homework0813/MethodPrivateTest.java
+// 通用工具:反射调用任意权限方法(getDeclaredMethod + setAccessible + invoke)
+public static Object invokeAnyMethod(Object obj, String methodName, Object... args) throws Exception {
+    // 1. 计算参数类型数组
+    Class<?>[] argsClasses = new Class[args.length];
+    for (int i = 0; i < args.length; i++) {
+        argsClasses[i] = args[i].getClass();
+    }
+    // 判断,obj 到底是什么类型,如果 obj 属于 Class 类型,无论是哪个类的 Class 对象,都会进入到 if 语句中
+    if (obj instanceof Class<?>) {
+        // 强转,把 obj 转换成 Class 类型
+        Class<?> clazz = (Class<?>) obj;
+        // 2. getDeclaredMethod(methodName, 参数类型...) 获取方法(含 private)
+        Method method = clazz.getDeclaredMethod(methodName, argsClasses);
+        // 3. setAccessible(true) 暴力反射放开权限
+        method.setAccessible(true);
+        // 4. invoke(null, args) 调用并返回结果 —— 静态方法第一参数传 null
+        return method.invoke(null, args);
+    }
+    // 如果 obj 不是 Class 类型,说明传入的是其他的对象,要调用的也是实例方法,而不是静态方法
+    Class<?> clazz = obj.getClass();
+    // 2. getDeclaredMethod(methodName, 参数类型...) 获取方法(含 private)
+    Method method = clazz.getDeclaredMethod(methodName, argsClasses);
+    // 3. setAccessible(true) 暴力反射放开权限
+    method.setAccessible(true);
+    // 4. invoke(obj, args) 调用并返回结果 —— 实例方法第一参数传对象
+    return method.invoke(obj, args);
+}
+```
+
+### 工具执行流程
+
+```
+invokeAnyMethod(student, "read")               invokeAnyMethod(student.getClass(), "write")
+      ↓                                                 ↓
+args 为空 → argsClasses 为空数组                  args 为空 → argsClasses 为空数组
+      ↓                                                 ↓
+obj instanceof Class<?> ? 否                    obj instanceof Class<?> ? 是(student.getClass() 是 Class 对象)
+      ↓                                                 ↓
+clazz = obj.getClass() = Student.class          clazz = (Class<?>) obj = Student.class
+      ↓                                                 ↓
+getDeclaredMethod("read", 无参)                  getDeclaredMethod("write", 无参)
+      ↓                                                 ↓
+setAccessible(true)                              setAccessible(true)
+      ↓                                                 ↓
+invoke(student, )   ← 实例方法传对象              invoke(null, )      ← 静态方法传 null
+```
+
+### 两个核心技巧拆解
+
+| 技巧 | 代码 | 作用 | 说明 |
+|------|------|------|------|
+| **① 动态计算参数类型** | `argsClasses[i] = args[i].getClass()` | 根据实际传入的参数**动态推导**参数类型数组 | 不写死 `String.class` 等——传什么参数就自动得到什么类型,方法有重载也能精确定位 |
+| **② instanceof 判断实例/静态** | `obj instanceof Class<?>` | 判断 obj 是 Class 对象还是普通对象 | 传 `student.getClass()` 说明要调**静态方法**;传普通对象说明要调**实例方法** |
+| 静态方法分支 | `invoke(null, args)` | 静态方法调用 | obj 是 Class → 方法属于整个类 → invoke 传 null |
+| 实例方法分支 | `invoke(obj, args)` | 实例方法调用 | obj 是普通对象 → 方法属于该对象 → invoke 传 obj |
+
+### 为什么用 `instanceof Class<?>` 判断?
+
+`student.getClass()` 返回的是 `Class<Student>` 类型对象——它既是"用来拿方法元数据的类描述",又被当作"要调静态方法的信号"。工具里用 `obj instanceof Class<?>` 判断:**obj 是 Class 类型 → 传进来的是"类",调静态方法;obj 不是 Class 类型 → 传进来的是"对象",调实例方法**。注释里专门解释:"如果 obj 属于 Class 类型,无论是哪个类的 Class 对象,都会进入到 if 语句中"——所以传 `student.getClass()` 和传 `Student.class` 效果一样。
+
+> **要点**:
+> - **`args[i].getClass()` 动态计算参数类型**:上一课练习六的经典技巧——通用工具不能写死参数类型,**先根据实际参数用 `getClass()` 算出参数类型数组**,再传给 `getDeclaredMethod(methodName, argsClasses)`;`args` 是可变参数(`Object...`),长度为 0 时得到空数组
+> - **`instanceof Class<?>` 是"静态方法开关"**:`Class` 类型的 obj 走静态分支(invoke(null)),普通对象走实例分支(invoke(obj))——同一个工具两种用法,判断依据就是 obj 是不是 Class
+> - **两个分支代码几乎一样**:都是"getDeclaredMethod + setAccessible + invoke",**只有 invoke 第一参数不同**(null vs obj)——这也再次说明实例/静态方法的唯一差别就在"要不要传对象"
+> - **返回值 Object**:`invoke` 返回方法执行结果(Object 类型),工具把结果 return 给调用者——本课两个方法都是 void,所以返回 null,但工具设计上支持有返回值的方法
+
+---
+
+## 7. MiniJUnitTest 演示:模拟 JUnit 单元测试框架(反射终极应用)
+
+### 概念 —— 测试框架的底层原理:遍历方法 + 前缀识别 + 反射执行
+
+JUnit 等测试框架的核心原理就是反射:**拿到类 → 遍历全部方法 → 找出测试方法 → 反射执行并统计结果**。真实 JUnit 用 `@Test` **注解**标记测试方法,MiniJUnit 用**方法名前缀 "test"** 识别——原理完全一致。整个框架只有 6 步:
+
+```java
+// 来源:homework0813/MiniJUnitTest.java
+public class MiniJUnitTest {
+    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
+        // 1. 反射创建被测 Student 对象(getConstructor().newInstance())
+        Class<Student> studentClass = Student.class;
+        Student student = studentClass.getConstructor().newInstance();
+        // 2. getDeclaredMethods() 遍历 Student 全部方法
+        Method[] declaredMethods = studentClass.getDeclaredMethods();
+        // 3. 方法名以 "test" 开头 → 认定为测试方法
+        int count = 0, a = 0;
+        for (Method m : declaredMethods) {
+            String methodName = m.getName();
+            if (methodName.startsWith("test")) {
+                // 4. 对每个测试方法:setAccessible(true) + invoke(obj) 执行
+                m.setAccessible(true);
+                try {
+                    m.invoke(student);
+                    // 5. 捕获 InvocationTargetException → getCause() 打印真实异常
+                    a++;                       // 执行成功 → 通过数 +1
+                } catch (IllegalAccessException e) {
+                    count++;                   // 权限异常 → 失败数 +1
+                } catch (InvocationTargetException e) {
+                    count++;                   // 被调方法内部抛异常(包装) → 失败数 +1
+                }
+            }
+        }
+        // 6. 统计通过/失败个数
+        System.out.println("失败个数:" + count);
+        System.out.println("成功个数:" + a);
+    }
+}
+```
+
+### 预期输出
+
+```
+测试:加法计算
+测试:打招呼 null
+失败个数:1
+成功个数:2
+```
+
+> 说明:`testAdd` 通过(输出"测试:加法计算")、`testHello` 通过(输出"测试:打招呼 null"——用无参构造创建对象,name 为 null)、`testFail` 故意抛异常判失败——所以统计"失败 1 个、成功 2 个"。
+
+### MiniJUnit 框架六步流程
+
+```
+Student.class                              ← ① 拿 Class 对象(类名.class)
+      ↓
+getConstructor().newInstance()             ← ② 反射创建被测对象(无参构造)
+      ↓
+getDeclaredMethods()                       ← ③ 遍历本类全部方法(含 private)
+      ↓
+methodName.startsWith("test")              ← ④ 按前缀识别测试方法(真实 JUnit 用 @Test 注解)
+      ↓
+setAccessible(true) + invoke(student)      ← ⑤ 暴力反射执行测试方法
+      ↓
+成功 a++ / 失败 count++                     ← ⑥ 统计通过/失败个数
+```
+
+> **要点**:
+> - **MiniJUnit 就是简化版 JUnit**:真实 JUnit 的 `@Test` 注解识别 → 底层就是"遍历方法 + 判断是否测试方法";MiniJUnit 用 `startsWith("test")` 替代注解,**原理一模一样**
+> - **测试方法都是 private**:MiniJUnit 能执行 private 的 testXxx 方法,靠的就是 `setAccessible(true)` 暴力反射——**测试框架破坏封装访问私有方法**是反射破坏封装性的典型正面应用
+> - **main 抛出 4 个受检异常**:`NoSuchMethodException`(getConstructor 找不到无参构造)、`InvocationTargetException`(被调方法抛异常)、`InstantiationException`(实例化失败)、`IllegalAccessException`(权限问题)——反射常用受检异常的"全家福"
+
+---
+
+## 8. 反射创建被测对象:getConstructor().newInstance()
+
+### 概念 —— 不知道类也能创建对象,靠 Class + Constructor
+
+框架要测试一个类,**编译期并不知道被测类是谁**(可能是用户后来写的)——所以不能 `new`,必须反射创建对象:`getConstructor()` 拿到**无参构造方法**,再 `.newInstance()` 创建实例:
+
+```java
+// 来源:homework0813/MiniJUnitTest.java(第 1 步)
+Class<Student> studentClass = Student.class;              // ① 拿 Class 对象
+Student student = studentClass.getConstructor().newInstance();   // ② getConstructor() 拿无参构造 → newInstance() 创建对象
+```
+
+### 代码拆解
+
+| 代码 | 作用 | 说明 |
+|------|------|------|
+| `Student.class` | 获取 Student 的 Class 对象 | 方式一:类名.class(上一课:三种获取 Class 对象的方式) |
+| `studentClass.getConstructor()` | 获取**public 无参构造方法**对象 | 不传参数 → 匹配无参构造;Student 有无参构造 `public Student() {}` |
+| `.newInstance()` | **反射创建对象** | 等价于 `new Student()`——但运行时才知道创建的是哪个类 |
+
+> **要点**:
+> - **`getConstructor()` 拿的是 public 无参构造**:如果不传参数类型,默认找无参构造;Student 定义了无参构造,所以能成功——**如果类没有无参构造,这里会抛 NoSuchMethodException**
+> - **为什么用无参构造**:测试框架不知道被测类有哪些字段、怎么传参,所以**统一用无参构造创建对象**(对象字段都是默认值 null/0),再通过 setter 或反射赋测试数据
+> - **上一条的连锁反应**:无参构造创建的对象 name 是 null → 执行 `testHello()` 输出"测试:打招呼 null"——这正是"字段默认值"的体现
+> - **Constructor.newInstance() vs Class.newInstance()**:JDK9+ 推荐用 `Constructor.newInstance()`(可访问 private 构造、可传参),本课用 `getConstructor()` 系列(上一课 08-12 已学)
+
+---
+
+## 9. getDeclaredMethods() 遍历 + test 前缀识别测试方法
+
+### 概念 —— 遍历全部方法,用命名约定找出"测试方法"
+
+测试框架的核心问题:**怎么知道哪些方法是测试方法?** 真实 JUnit 用 `@Test` 注解,MiniJUnit 用**命名约定**——方法名以 `test` 开头的就当作测试方法。遍历用 `getDeclaredMethods()`(拿本类全部方法,含 private):
+
+```java
+// 来源:homework0813/MiniJUnitTest.java(第 2~3 步)
+Method[] declaredMethods = studentClass.getDeclaredMethods();   // ① 遍历本类全部方法(含 private)
+int count = 0, a = 0;                                           // ② 失败计数器 count、成功计数器 a
+for (Method m : declaredMethods) {
+    String methodName = m.getName();
+    if (methodName.startsWith("test")) {                        // ③ 方法名以 "test" 开头 → 认定为测试方法
+        // …… 执行测试(见第 10 节)……
+    }
+}
+```
+
+### 代码拆解
+
+| 代码 | 作用 | 说明 |
+|------|------|------|
+| `studentClass.getDeclaredMethods()` | 获取**本类声明的全部方法**数组 | 含 private(testXxx 都是 private),不含父类 Object 的方法 |
+| `m.getName()` | 获取方法名 | 如 `testAdd` / `testHello` / `testFail` / `getName` / `read` 等 |
+| `methodName.startsWith("test")` | 判断方法名是否以 test 开头 | **命名约定识别测试方法**——true 才进入执行逻辑 |
+| `int count = 0, a = 0` | 失败/成功计数器 | count 记失败、a 记成功(`a` 是 success 简写) |
+
+### 为什么能遍历到 private 的 testXxx?
+
+`testAdd`/`testHello`/`testFail` 都是 **private** 方法——普通代码在类外部根本看不到它们。但 `getDeclaredMethods()` **包含本类声明的全部方法(含 private)**,所以反射能拿到这 3 个测试方法。这正是测试框架能测试"类内部私有逻辑"的基础:**私有方法无法从外部调用,但反射遍历 + 暴力反射可以**。
+
+> **要点**:
+> - **命名约定 vs 注解**:JUnit 早期版本(JUnit 3)就是用"方法名以 test 开头"识别测试方法,后来 JUnit 4 才改用 `@Test` 注解——MiniJUnit 模拟的是**最古老的 JUnit 3 设计**,更直观地展示"框架如何识别测试方法"
+> - **getDeclaredMethods 不含父类方法**:只遍历 Student 本类声明的方法(含 3 个 testXxx),不会把 Object 的 toString/hashCode 等也遍历出来
+> - **if 判断是过滤条件**:多个方法里只有 3 个 test 开头的方法进入执行分支——这就是"扫描 + 筛选"的框架思路
+
+---
+
+## 10. setAccessible(true) + invoke 执行测试方法
+
+### 概念 —— 暴力反射执行 private 测试方法,异常决定成败
+
+识别出测试方法后,执行也是**暴力反射三件套**:`setAccessible(true)` 放开权限 → `invoke(student)` 执行。**成败判定靠异常**:`invoke` 不抛异常 = 测试通过(a++);抛异常 = 测试失败(count++):
+
+```java
+// 来源:homework0813/MiniJUnitTest.java(第 4~5 步)
+for (Method m : declaredMethods) {
+    String methodName = m.getName();
+    if (methodName.startsWith("test")) {
+        m.setAccessible(true);              // ① 暴力反射:放开 private 测试方法的权限
+        try {
+            m.invoke(student);              // ② 执行测试方法(invoke 传被测对象)
+            a++;                            // ③ 不抛异常 → 成功 +1
+        } catch (IllegalAccessException e) {
+            count++;                        // ④ 权限异常 → 失败 +1
+        } catch (InvocationTargetException e) {
+            count++;                        // ⑤ 被调方法内部抛异常(包装) → 失败 +1
+        }
+    }
+}
+```
+
+### 三种情况对照
+
+| 测试方法 | invoke 结果 | 进入哪个分支 | 计数器 |
+|----------|-------------|--------------|--------|
+| `testAdd()` | 正常执行(1+1==2 不抛异常) | try 成功 | 成功 a++ |
+| `testHello()` | 正常执行(只是打印) | try 成功 | 成功 a++ |
+| `testFail()` | 内部 `throw new RuntimeException` → invoke **抛 InvocationTargetException** | catch InvocationTargetException | 失败 count++ |
+
+> **要点**:
+> - **测试框架的"断言"哲学**:测试方法内部写断言(if 条件不满足就 throw),invoke 执行时一旦抛异常就被捕获并判为失败——**"不抛异常 = 通过"是测试框架最基本的判定规则**
+> - **为什么要捕获异常而不是让程序崩溃**:任何一个测试失败都不应该中断整个测试套件——所以 try-catch 包住 invoke,**单个测试失败只计一次失败,继续执行下一个测试**
+> - **IllegalAccessException 也被计入失败**:理论上 setAccessible(true) 后不会抛权限异常,但防御性编程把两种异常都捕获计入失败——保证任何异常都影响统计而不影响流程
+> - **invoke 第一参数传被测对象**:`m.invoke(student)` 的 student 是第 8 节反射创建的对象——测试方法都是实例方法,必须传对象
+
+---
+
+## 11. InvocationTargetException 包装异常:getCause() 获取真实异常
+
+### 概念 —— 被调方法内部抛异常时,invoke 会"包装"而不是"直接抛"
+
+这是反射调用与普通调用最隐蔽的差异:**普通代码 `s.testFail()` 会直接抛 RuntimeException;但反射 `invoke` 调用时,被调方法内部抛的异常会被包装成 `InvocationTargetException` 抛出**。因为 invoke 是"反射机制"调用的方法,它在被调方法外面包了一层——调用者必须通过 `e.getCause()` 才能拿到真正的异常。
+
+```java
+// 来源:homework0813/MiniJUnitTest.java(第 5 步:InvocationTargetException 捕获)
+try {
+    m.invoke(student);
+    a++;                       // 不抛异常 → 成功
+} catch (IllegalAccessException e) {
+    count++;                   // 权限异常 → 失败
+} catch (InvocationTargetException e) {
+    count++;                   // 被调方法内部抛异常(InvocationTargetException 包装) → 失败
+    // 如果要看真实异常:e.getCause() 返回真正的 RuntimeException("故意失败的方法")
+}
+```
+
+### 异常包装流程
+
+```
+testFail() 内部 throw new RuntimeException("故意失败的方法")
+      ↓
+m.invoke(student) 执行 testFail()
+      ↓
+被调方法抛异常 → 反射机制把异常"包了一层"
+      ↓
+invoke 抛出 InvocationTargetException(不是直接抛 RuntimeException)
+      ↓
+catch (InvocationTargetException e) 捕获 → count++ 记为失败
+      ↓
+真实异常:e.getCause()  ← 拿到真正的 RuntimeException("故意失败的方法")
+```
+
+| 对比 | 普通调用 | 反射调用(invoke) |
+|------|----------|---------------------|
+| 调用方式 | `student.testFail()` | `m.invoke(student)` |
+| 被调方法抛异常时 | **直接抛 RuntimeException** | **抛 InvocationTargetException(包装)** |
+| 如何拿到真实异常 | 直接 catch RuntimeException | `catch (InvocationTargetException e)` 后 `e.getCause()` |
+| 为什么不直接抛 | — | invoke 是反射机制,被调方法外有"包装层" |
+
+> **要点**:
+> - **InvocationTargetException 是"包装异常"**:它的名字直译是"调用目标异常"——被调目标(testFail)抛了异常,被 invoke 包了一层再抛出来
+> - **getCause() 获取真实异常**:需要诊断失败原因时,用 `e.getCause()` 拿到真正的 RuntimeException(及异常消息)——注释 5 特意提醒"getCause() 打印真实异常"
+> - **MiniJUnit 只统计不计原因**:本课框架只 count++ 计失败数,没有打印 getCause() 详情——实际 JUnit 会把 getCause() 的异常栈打印出来,这就是**测试报告里"失败原因"的来源**
+> - **与 08-13 练习的呼应**:上一课练习里就有"把 read() 改成会抛异常的语句,观察 invoke 抛 InvocationTargetException"的实验——本课把这一现象用在了真实框架逻辑里
+
+---
+
+## 12. 通过/失败统计:模拟测试报告输出
+
+### 概念 —— 框架的最后一步:汇总成功/失败个数
+
+所有测试方法执行完后,输出统计结果——这就是简化版"测试报告":
+
+```java
+// 来源:homework0813/MiniJUnitTest.java(第 6 步)
+System.out.println("失败个数:" + count);   // 输出 失败个数:1
+System.out.println("成功个数:" + a);       // 输出 成功个数:2
+```
+
+### 预期输出
+
+```
+测试:加法计算
+测试:打招呼 null
+失败个数:1
+成功个数:2
+```
+
+### 运行流程全景(MiniJUnit 完整执行)
+
+```
+第 1 步:反射创建被测对象  → Student student(name=null)
+第 2 步:遍历全部方法       → getDeclaredMethods()
+第 3 步:筛选 test 前缀      → 命中 testAdd / testHello / testFail
+第 4 步:逐个暴力反射执行:
+    testAdd   →  输出"测试:加法计算",1+1==2 不抛异常  → 成功 a=1
+    testHello → 输出"测试:打招呼 null"(name 为 null)→ 成功 a=2
+    testFail  →  输出后直接 throw → invoke 抛 InvocationTargetException → 失败 count=1
+第 5 步:统计输出 → 失败个数:1,成功个数:2
+```
+
+### 计数器拆解
+
+| 变量 | 含义 | 最终值 | 对应测试方法 |
+|------|------|--------|--------------|
+| `a` | 成功个数 | 2 | testAdd(通过)、testHello(通过) |
+| `count` | 失败个数 | 1 | testFail(故意抛异常) |
+
+> **要点**:
+> - **两个计数器语义**:`a` 记成功(invoke 不抛异常)、`count` 记失败(IllegalAccessException 或 InvocationTargetException)——一正一负互补,相加等于测试方法总数 3
+> - **"测试:打招呼 null"是合理的**:testHello 用反射创建的无参构造对象执行,name 是 null——这提示"测试对象需要先初始化数据",真实测试框架会提供 @BeforeEach 之类的初始化钩子
+> - **模拟测试报告的局限**:真实 JUnit 还会输出每个测试的耗时、失败栈、@Test 注解解析等——MiniJUnit 只演示了**最核心的"扫描 + 执行 + 统计"闭环**
+
+---
+
+## 13. 知识点全景总结
+
+| 知识点 | 核心内容 | 来源 |
+|--------|----------|------|
+| **项目结构** | `c260814` 项目 = `homework0813` 包(0813 作业进阶)——三个类:Student(反射目标实体类)/ MethodPrivateTest(暴力反射调用私有方法 + 通用工具)/ MiniJUnitTest(模拟 JUnit 单元测试框架) | homework0813/ |
+| **Student 三种权限字段** | private(id/name/age/gender)+ public(email/phone)+ public static(school)——上一课 Field 反射目标,本课复用 | homework0813/Student.java |
+| **Student 4 个构造方法** | public 无参 / private 二参 / public 四参 / public 六参——MiniJUnit 用 public 无参构造反射创建对象 | homework0813/Student.java |
+| **Student 私有方法 read/write** | `private void read()`(实例)+ `private static void write()`(静态)——MethodPrivateTest 暴力反射调用目标 | homework0813/Student.java |
+| **Student 测试方法 testAdd/testHello/testFail** | 3 个 `test` 前缀 private 方法——MiniJUnit 按命名约定识别的"模拟测试用例";testAdd 通过、testHello 通过、testFail 故意抛异常 | homework0813/Student.java |
+| **私有方法无公开入口** | read()/write()/testXxx 都是 private 且没有 getter/setter,普通代码无法调用——只有暴力反射能调用,这是本课"为什么需要反射"的场景 | homework0813/Student.java |
+| **暴力反射调用私有实例方法** | `getDeclaredMethod("read")`(拿 private)+ `setAccessible(true)`(放开权限)+ `invoke(对象)`(第一参数传调用该方法的对象)——三件套与 Field/Constructor 一致 | homework0813/MethodPrivateTest |
+| **暴力反射调用私有静态方法** | `getDeclaredMethod("write")` + `setAccessible(true)` + `invoke(null)`(静态方法第一参数传 null)——private 决定暴力反射、static 决定传 null,两个维度独立 | homework0813/MethodPrivateTest |
+| **invoke 第一参数规律** | **实例方法传对象、静态方法传 null**——与 Field 操作静态属性传 null 同一规律:静态成员属于整个类、不属于任何对象 | homework0813/MethodPrivateTest |
+| **通用工具 invokeAnyMethod** | 封装"计算参数类型 → 获取方法 → 放开权限 → 调用"四步——`invokeAnyMethod(student, "read")` 调实例方法、`invokeAnyMethod(student.getClass(), "write")` 调静态方法 | homework0813/MethodPrivateTest |
+| **instanceof Class<?> 判断** | `obj instanceof Class<?>` 判断 obj 是 Class 对象还是普通对象——Class → 静态方法分支 invoke(null);普通对象 → 实例方法分支 invoke(obj) | homework0813/MethodPrivateTest |
+| **args[i].getClass() 动态计算参数类型** | 通用工具不能写死参数类型,用 `args[i].getClass()` 根据实际参数动态算出 `Class<?>[]` 再传给 getDeclaredMethod——可变参数 Object... 长度为 0 时得到空数组 | homework0813/MethodPrivateTest |
+| **反射创建被测对象** | `studentClass.getConstructor().newInstance()`——getConstructor() 拿 public 无参构造(不传参数类型默认找无参构造),newInstance() 创建对象;类没有无参构造会抛 NoSuchMethodException | homework0813/MiniJUnitTest |
+| **getDeclaredMethods() 遍历** | 获取本类声明的全部方法数组(含 private,不含父类 Object 的方法)——测试框架扫描测试方法的入口 | homework0813/MiniJUnitTest |
+| **test 前缀识别测试方法** | `methodName.startsWith("test")` 按命名约定识别测试方法——模拟 JUnit 3 的设计(JUnit 4+ 用 @Test 注解,原理相同) | homework0813/MiniJUnitTest |
+| **暴力反射执行测试方法** | `m.setAccessible(true)` + `m.invoke(student)`——private 测试方法靠暴力反射执行;invoke 不抛异常 = 通过(a++),抛异常 = 失败(count++) | homework0813/MiniJUnitTest |
+| **"不抛异常 = 通过"的判定哲学** | 测试方法内部写断言(if 条件不满足就 throw),invoke 执行时抛异常即判失败——测试框架最基本的判定规则 | homework0813/MiniJUnitTest |
+| **InvocationTargetException 包装异常** | 被调方法内部抛异常时,反射 invoke 不会直接抛原异常,而是**包装成 InvocationTargetException 抛出**;调用者需 `e.getCause()` 获取真实异常 | homework0813/MiniJUnitTest |
+| **InvocationTargetException 与普通调用对比** | 普通 `student.testFail()` 直接抛 RuntimeException;反射 `m.invoke(student)` 抛 InvocationTargetException(被调方法外有包装层) | homework0813/MiniJUnitTest |
+| **通过/失败统计** | 成功计数器 a、失败计数器 count——testAdd/testHello 通过(a=2),testFail 失败(count=1),输出"失败个数:1,成功个数:2" | homework0813/MiniJUnitTest |
+| **测试报告输出** | `System.out.println("失败个数:" + count)` / `System.out.println("成功个数:" + a)`——简化版测试报告 | homework0813/MiniJUnitTest |
+| **受检异常全家福** | MiniJUnit main throws `NoSuchMethodException`(无无参构造)+ `InvocationTargetException`(被调方法抛异常)+ `InstantiationException`(实例化失败)+ `IllegalAccessException`(权限) | homework0813/MiniJUnitTest |
+| **防御性异常捕获** | catch 里同时处理 IllegalAccessException 和 InvocationTargetException 都计入失败——保证单个测试失败不影响后续测试执行 | homework0813/MiniJUnitTest |
+| **方法反射综合应用闭环** | 获取方法(getDeclaredMethod(s))→ 暴力反射(setAccessible)→ 调用(invoke)→ 异常处理(InvocationTargetException.getCause())→ 统计——完整走完 Method 反射的全部环节 | homework0813/MethodPrivateTest + MiniJUnitTest |
+
+---
+
+## 14. 随堂练习要点
+
+- **项目结构梳理(必做)**:说清 c260814 项目三个类各司其职——Student 提供被测对象与测试方法、MethodPrivateTest 演示暴力反射调用私有方法并封装通用工具、MiniJUnitTest 模拟 JUnit 测试框架;包名 homework0813 的含义(0813 作业进阶)
+- **Student 私有方法识别(必做)**:从 Student 类中找出所有 private 方法(read/write/testAdd/testHello/testFail 共 5 个),区分实例方法(read/testXxx)与静态方法(write)——思考为什么普通代码调用不了它们,只有反射能调
+- **暴力反射三件套背诵(必做)**:`getDeclaredMethod("read")`(拿 private)+ `setAccessible(true)`(放开权限)+ `invoke(对象)`(实例方法传对象)——对比静态方法 `invoke(null)`,总结"private 决定暴力反射、static 决定传 null"
+- **invoke 第一参数规律实验**:把 `invokeAnyMethod(student.getClass(), "write")` 改成 `invokeAnyMethod(student, "write")`(传对象不传 Class)——观察工具走到实例方法分支、invoke(student) 调静态方法,思考结果是否仍能执行(静态方法不依赖对象,通常也能跑)——体会"传 Class 才是调用静态方法的正确信号"
+- **instanceof Class<?> 理解(必做)**:思考通用工具为什么用 `obj instanceof Class<?>` 判断——`student.getClass()` 和 `Student.class` 都是 Class 对象;传 Class → 静态方法分支,传普通对象 → 实例方法分支;画一张"调用流程图"说明两个分支的执行路径
+- **args[i].getClass() 动态计算参数类型**:给 Student 新增一个有参方法(如 `testAdd(int a, int b)`),用 `invokeAnyMethod(student, "testAdd", 1, 2)` 调用——体会工具自动用 `args[i].getClass()` 算出参数类型数组(自动装箱后实际是 Integer.class)
+- **setAccessible(true) 注释掉实验(必做)**:把 MiniJUnit 中 `m.setAccessible(true)` 注释掉再运行——观察 3 个 private 测试方法全部抛 IllegalAccessException,统计"失败 3 个、成功 0 个"——直观体会暴力反射对私有方法的作用
+- **getDeclaredMethods vs getMethods 对比**:把 MiniJUnit 中 `getDeclaredMethods()` 改成 `getMethods()` 再运行——观察 testXxx(private)遍历不到,成功/失败都是 0——验证"getMethods 只拿 public、拿不到 private 测试方法"
+- **InvocationTargetException 实验(必做)**:在 testFail 的 catch 分支里加 `e.getCause().printStackTrace()` 再运行——观察控制台打印出真正的 RuntimeException("故意失败的方法")——体会 getCause() 获取真实异常、测试报告"失败原因"的来源
+- **MiniJUnit 扩展练习①:新增测试方法**:给 Student 新增 `testSub()`(减法测试,`2-1==1` 通过)和 `testDiv()`(故意 `1/0` 抛异常失败)——重新运行观察统计变为"成功 3、失败 2"
+- **MiniJUnit 扩展练习②:打印测试结果明细**:在 invoke 成功后加 `System.out.println(methodName + " 通过")`、失败后加 `System.out.println(methodName + " 失败:" + e.getCause())`——让模拟测试报告显示每个用例的名字和失败原因(贴近真实 JUnit)
+- **无参构造的重要性(必做)**:把 Student 的 `public Student() {}` 注释掉再运行 MiniJUnit——观察 `getConstructor().newInstance()` 抛 NoSuchMethodException——理解"反射创建对象为什么要求类有无参构造"(框架不知道传什么参数)
+- **测试对象初始化问题**:观察"测试:打招呼 null"的输出——反思:无参构造创建的对象字段为 null,真实测试框架如何解决初始化?(提示:@BeforeEach / @BeforeAll 钩子方法,或反射 setter 注入测试数据)
+- **防御性异常处理思考**:MiniJUnit 把 IllegalAccessException 和 InvocationTargetException 都计入失败——思考还有什么异常可能抛出(如 InvocationTargetException 的 getCause 是 Error)、框架如何处理才算健壮
+- **与真实 JUnit 对比(必做)**:说出 MiniJUnit 与真实 JUnit 的 3 个差异——①测试方法识别:test 前缀 vs @Test 注解 ②没有 @BeforeEach/@AfterEach 初始化/清理钩子 ③失败时只统计个数、不打印失败原因和堆栈
+- **通用工具复用(必做)**:把 invokeAnyMethod 抄下来作为自己的反射工具方法——思考它还能调用哪些方法(任意对象的任意方法:getter/setter/私有方法/静态方法),回顾上一课练习六的 invokeMethod 工具与本课工具的异同(本课多了 instanceof 判断静态/实例分支)
+- **Method 反射知识串联(本课完结)**:从 08-12 Constructor(创建对象)→ 08-13 Field(读写字段)+ Method(调用方法)→ 本课 Method 进阶应用(暴力反射私有方法 + 模拟测试框架)——完整回顾反射三大成员从"学 API"到"做框架"的进阶路径
+
+---
+
+## 15. 拓展阅读
+
+- **JUnit 的底层原理**:JUnit 5(Jupiter)通过 `@Test` 注解 + 反射实现测试执行——测试类被 `Class.forName` 加载 → 遍历方法找 `@Test` 注解 → `setAccessible(true)` + `invoke` 执行 → 用 `InvocationTargetException.getCause()` 捕获断言异常并生成测试报告——本课 MiniJUnit 正是这个链路的简化版
+- **JUnit 3 与 JUnit 4/5 的演进**:JUnit 3 用"方法名以 test 开头"识别测试(本课模拟的就是 JUnit 3 设计);JUnit 4 引入 `@Test` 注解(注解驱动);JUnit 5 引入扩展模型(Extension)——理解"命名约定 → 注解 → 扩展模型"的演进,是理解测试框架历史的钥匙
+- **断言(Assertion)机制**:真实 JUnit 用 `Assert.assertEquals(expected, actual)` 等断言方法——断言内部就是"判断条件,不满足就 throw AssertionError";反射 invoke 执行测试方法时,断言异常被包装成 InvocationTargetException,框架用 getCause() 取出断言信息作为"测试失败原因"
+- **生命周期钩子方法**:真实 JUnit 的 `@BeforeEach`/`@AfterEach`/`@BeforeAll`/`@AfterAll` 注解方法——框架在"每次测试前/后、全部测试前/后"反射调用这些钩子方法——本课"测试:打招呼 null"暴露的对象初始化问题,正是 @BeforeEach 要解决的
+- **反射在框架中的"扫描 + 执行"模式**:本课 MiniJUnit 的"getDeclaredMethods 遍历 → 条件筛选 → invoke 执行"是反射框架的**通用范式**——Spring 的组件扫描(找 @Component 注解的类)、MyBatis 的 Mapper 解析(找接口方法)、参数解析器(找匹配参数)都是这个套路
+- **InvocationTargetException 在框架中的应用**:任何"反射调用业务方法"的框架(Spring AOP 代理方法、RPC 框架调用远程服务方法、定时任务框架)都会遇到 InvocationTargetException——框架统一用 `getCause()` 拿到业务异常再处理,避免被"包装层"迷惑
+- **getCause() 的链式异常**:`getCause()` 返回的异常可能还有自己的 cause(异常链)——框架(如 JUnit)打印失败栈时会遍历整条异常链 `while ((e = e.getCause()) != null)`,把每一层原因都展示出来
+- **反射调用 vs 方法句柄(MethodHandle)**:JDK7+ 引入 `java.lang.invoke.MethodHandle`(方法句柄),性能优于反射 invoke,是"更高性能的反射"——Spring、Netty 等框架在热点路径上会用 MethodHandle 替代 Method.invoke
+- **反射调用 setter/getter 的框架应用**:Spring BeanUtils.copyProperties、MyBatis 结果映射、Jackson 反序列化都通过反射调用实体类的 setter 赋值——这就是实体类必须写标准 getter/setter 的原因;本课 Student 的 7 组 getter/setter 正是为此准备
+- **Class.getConstructor vs getDeclaredConstructor**:MiniJUnit 用 `getConstructor()`(public 无参构造)——如果被测类的构造是 private,需要用 `getDeclaredConstructor()` + `setAccessible(true)`(上一课 08-12 已学);**框架统一要求无参构造**,就是为了避免处理各种构造器参数
+- **"无参构造"约定在框架中的地位**:Spring Bean、MyBatis 实体、JUnit 测试类都要求无参构造——因为框架无法预知构造参数,统一用无参构造 + setter 注入是最通用的约定;这也是 JavaBean 规范(无参构造 + getter/setter)的由来
+- **测试框架中的"隔离"思想**:为什么每个测试方法失败不中断整个套件?——因为测试之间应**相互独立**(一个测试失败不代表其他测试失效);try-catch 包住每个 invoke 正是"测试隔离"的实现
+- **反射破坏封装性的辩证关系(最终版)**:从 08-12 的 Constructor(newInstance 创建对象)到 08-13 的 Field(get/set 读写字段)+ Method(invoke 调用方法)再到本课的"暴力反射私有方法 + 测试框架"——反射破坏封装的"负面"(能访问 private)恰恰是框架"正面"能力(测试私有逻辑)的来源:**普通业务代码遵守封装,框架用反射突破封装**
+- **反射 Method 知识全景(本课收官)**:Method 反射的全部技能点——获取方法(getMethod(s)/getDeclaredMethod(s))、方法信息(getName/getReturnType/getParameterTypes/getModifiers)、调用方法(invoke)、暴力反射(setAccessible)、异常处理(InvocationTargetException.getCause())、组合应用(通用调用工具 + 测试框架)——至此"反射三件套"(Constructor/Field/Method)全部学完并可组合成框架级工具
+- **JDK 文档**:[`java.lang.Class`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Class.html)(getConstructor/getDeclaredMethods 等反射入口)、[`java.lang.reflect.Method`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/reflect/Method.html)(invoke/setAccessible/getName/getParameterTypes)、[`java.lang.reflect.InvocationTargetException`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/reflect/InvocationTargetException.html)(包装被调方法异常的反射专用异常,getCause 获取真实异常)、[JUnit 5 User Guide](https://junit.org/junit5/docs/current/user-guide/)(@Test 注解与扩展模型、断言机制)