Просмотр исходного кода

feat: 新增20260813课后作业——由课堂练习七/八迁移为课后作业,基础题题目1暴力反射调用私有方法与静态方法(getDeclaredMethod+setAccessible(true)+invoke实例方法传对象/静态方法传null,封装不区分权限的invokeAnyMethod通用工具)、进阶题题目2综合实战模拟测试框架JUnit简化版(getDeclaredMethods扫描test开头方法+setAccessible(true)+invoke调用+InvocationTargetException.getCause()处理真实异常+统计通过失败+反射三件套Constructor/Field/Method组合框架级流程),含作业总体要求/命名要求/实体类说明/提交方式/实现步骤/代码框架/输出示例/选做加分/思考题/涵盖知识点总览

WanJL 1 неделя назад
Родитель
Сommit
6c5d07685c
1 измененных файлов с 218 добавлено и 0 удалено
  1. 218 0
      课后作业/2026年8月13日-作业.md

+ 218 - 0
课后作业/2026年8月13日-作业.md

@@ -0,0 +1,218 @@
+# 2026年8月13日 课后作业 — 反射 Method:暴力反射调用私有方法 + 模拟测试框架
+
+> **说明**:本次课程学习了 **反射 Field(属性/字段/变量)**(Demo01:Class 类获取 Field 对象的 4 个 API、Field 类的常用方法 get/set/setAccessible/getType/getName/getModifiers、Modifier.toString 解析修饰符、遍历字段、操作 public/private/static 属性)以及 **反射 Method(方法)**(Demo02:Class 类获取 Method 对象的 4 个 API、重载方法必须传参数类型区分、Method 类的常用方法 invoke/setAccessible/getReturnType/getParameterTypes/getModifiers、**invoke 四种调用规律**——实例方法传对象 / 静态方法传 null / void 返回 null / private 先 setAccessible(true) 暴力反射、遍历方法输出方法名/参数/返回值、动态代理 / 测试框架 / 注解解释器的基础)。
+>
+> 本作业共 **2 道题(必做)**:**基础题**——暴力反射调用私有方法与静态方法(getDeclaredMethod + setAccessible(true) + invoke 实例传对象/静态传 null,封装不区分权限的 invokeAnyMethod 通用工具);**进阶题(综合实战)**——模拟测试框架 JUnit 简化版(getDeclaredMethods 扫描 test 开头方法 + 暴力反射调用 + InvocationTargetException.getCause() 处理 + 反射三件套)。覆盖今日"方法反射 invoke 四种调用规律 + 暴力反射 + 框架底层原理"核心知识点。
+
+---
+
+## 📌 作业总体要求
+
+### 一、命名要求
+
+| 项目 | 要求 |
+|------|------|
+| **类名** | `Demo_题号_你的姓名`(如 `Demo_01_张三`、`Demo_02_李四`) |
+| **文件头注释** | 每个 Java 文件头部需包含 `@author`、`@title`、`@create` 文档注释 |
+| **实体类** | 使用课程代码中的 `course.Student` 实体类(`授课代码/c260813/src/course/Student.java`,含私有方法 read()/write());题目 2 需在该类中补充 3 个 private 测试方法 |
+
+### 二、提交方式与截止时间
+
+| 项目 | 要求 |
+|------|------|
+| **提交方式** | 将源码文件 + 运行结果截图提交至班级指定平台 |
+| **截止时间** | 下次上课前 |
+
+---
+
+## 一、基础题(必做)
+
+### 题目 1:暴力反射调用私有方法与静态方法 —— 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、私有方法先暴力反射"。
+
+**题目要求**:
+
+```java
+/**
+ * @author 你的姓名
+ * @title 题目1:暴力反射调用私有方法与静态方法
+ * @create 2026/8/13
+ */
+```
+
+**实现步骤**:
+
+1. 创建 Student 对象,反射调用 setName("张三")(可用 `getMethod("setName", String.class)` + `invoke(s, "张三")`,或复用下面封装好的通用工具)
+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 Demo_01_你的姓名 {
+    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 的能拿私有")
+
+---
+
+## 二、进阶题(必做)
+
+### 题目 2:综合实战 —— 模拟测试框架(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()` 打印真实异常并统计"测试失败"
+
+——完整走一遍"反射三件套(创建对象/调方法)+ 暴力反射 + 异常处理"的**框架级流程**。
+
+**题目要求**:
+
+```java
+/**
+ * @author 你的姓名
+ * @title 题目2:模拟测试框架 JUnit 简化版
+ * @create 2026/8/13
+ */
+```
+
+**被测类准备**:在 `course.Student` 中补充 3 个 private 测试方法(或复制一份 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("故意失败的方法");
+}
+```
+
+**实现步骤**:
+
+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 Demo_02_你的姓名 {
+    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. 统计通过/失败个数
+    }
+}
+```
+
+**输出示例**:
+
+```
+找到测试方法: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 开头的方法才会被认定为测试方法)
+
+---
+
+# 涵盖知识点总览
+
+| 知识点 | 说明 |
+|--------|------|
+| `getDeclaredMethod("方法名", 参数类型...)` | 获取指定的单个方法(**包括 private**)——操作私有方法必须用它;`getMethod` 只能拿 public |
+| `method.setAccessible(true)` | **暴力反射**:临时取消访问权限检查——调用 private 方法必须调用,否则抛 IllegalAccessException |
+| `method.invoke(obj, 参数...)` | **调用方法**,返回方法执行结果(Object 可强转);等价于 `对象.方法(参数)` |
+| **invoke 四种调用规律** | ①实例方法 `invoke(对象, 参数...)` 第一参数传对象 ②静态方法 `invoke(null, 参数...)` 传 null ③void 方法 invoke 返回 null ④private 方法先 setAccessible(true) |
+| **静态方法 invoke 传 null** | 静态成员属于整个类、不属于任何对象——与 Field 操作静态属性传 null 同一规律 |
+| `getDeclaredMethods()` | 返回**本类所有方法**数组,**包括 private,但不包括父类/接口的方法**——扫描测试方法的入口 |
+| **InvocationTargetException** | invoke 调用方法时被调方法内部抛异常,invoke 把它**包装成 InvocationTargetException** 抛出——需要 `getCause()` 获取真正的异常 |
+| **反射三件套组合** | Constructor(newInstance 创建对象)+ Field(get/set 读写字段)+ Method(invoke 调用方法)——组合 = 框架级操作的完整底层链路 |
+| **模拟测试框架(JUnit 原理)** | `getDeclaredMethods()` 扫描方法 → `startsWith("test")` 过滤测试方法 → `setAccessible(true)` + `invoke(obj)` 执行 → 捕获 InvocationTargetException 统计通过/失败——JUnit 扫描 @Test 注解方法的底层套路 |
+| `getModifiers()` + `Modifier.toString()` | 获取方法修饰符 int 值并解析成可读字符串(private / private static...) |
+| **反射 API 的受检异常** | NoSuchMethodException(方法名/参数类型不匹配)/ IllegalAccessException(private 未放开权限)/ InvocationTargetException(被调方法内部抛异常)——必须处理(main 用 throws) |