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

feat: 新增20260812反射课堂练习——练习一反射创建对象(Class.forName+getConstructor+newInstance+instanceof强转)、练习二反射给私有属性赋值(Field反射getDeclaredField+setAccessible(true)暴力反射+field.set/get)、练习三反射调用方法(Method反射getMethod+invoke带参带返回值)、练习四综合实战配置文件驱动+反射(Properties加载类名/属性/方法名模拟Spring IOC创建对象+赋值+调用方法),含难度/知识点/实现步骤/代码框架TODO/输出示例/选做加分/思考题/涵盖知识点总览,同步更新README课堂练习题表

WanJL 1 неделя назад
Родитель
Сommit
ef6da815d5
2 измененных файлов с 388 добавлено и 0 удалено
  1. 1 0
      README.md
  2. 387 0
      课堂练习题/20260812-反射练习.md

+ 1 - 0
README.md

@@ -100,3 +100,4 @@
 | 2026-08-08 | [对象序列化与转换流练习](./课堂练习题/20260808-对象序列化与转换流练习.md) | 对象序列化/反序列化、Serializable、serialVersionUID、transient、转换流字符集转码 |
 | 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) | 反射创建对象(Class.forName + getConstructor + newInstance + instanceof + 强转)、反射给私有属性赋值(getDeclaredField + setAccessible + field.set/get)、反射调用方法(getMethod + invoke 带参返回值)、综合实战配置文件驱动 + 反射(模拟 Spring IOC:Properties 加载类名/属性/方法名) |

+ 387 - 0
课堂练习题/20260812-反射练习.md

@@ -0,0 +1,387 @@
+# 2026-08-12 反射练习(课堂练习用)
+
+> **说明**:练习今日新学知识点——**反射机制(Reflection)**(Demo04:反射概念、普通调用 vs 反射调用、为什么需要反射、反射核心类)、**获取 Class 对象三种方式**(Demo05:类名.class / 对象.getClass() / Class.forName("全类名"),同一个类三种方式拿到同一个对象)、**反射 Constructor 构造方法**(Demo06:getConstructors / getConstructor / getDeclaredConstructors / getDeclaredConstructor 获取构造方法、setAccessible(true) 暴力反射、newInstance() 反射创建对象、instanceof 判断 + 强转调用方法)。并延伸使用 **Field 字段反射**(给私有属性赋值)与 **Method 方法反射**(调用私有/带参方法)——它们是反射三大核心成员(Constructor / Field / Method)的后两者。
+
+---
+
+## 练习一:反射创建对象 —— 拿 Class → 拿构造 → 建对象
+
+**难度**:⭐⭐⭐
+**知识点**:`Class.forName("全类名")` 获取 Class 对象、`getConstructor()` 获取 public 无参构造、`newInstance()` 反射创建对象、`instanceof` 判断类型、向下转型、调用方法
+
+**场景描述**:课堂学了**反射最核心的用途**——拿到 Class 对象后,通过构造方法创建对象(这是 Spring IOC 等框架的底层原理)。本例完整走一遍反射创建对象的四步链:**① Class.forName 拿 Class → ② getConstructor 拿构造 → ③ newInstance 建对象 → ④ instanceof 判断 + 强转 + 调用方法**,与普通 `new Student()` 做对比。
+
+### 案例需求
+
+用反射的方式创建 Student 对象,并调用它的 study() 方法。
+
+### 实现步骤
+
+1. 用 `Class.forName("course.Student")` 获取 Student 的 Class 对象
+2. 用 `clazz.getConstructor()` 获取 public 无参构造方法对象
+3. 用 `constructor.newInstance()` 反射创建 Student 对象
+4. 用 `obj instanceof Student` 判断创建的对象是不是 Student 类型
+5. 强转成 Student 后调用 `study()` 方法
+6. 思考:反射创建对象 和 `new Student()` 有什么不同?
+
+### 代码框架
+
+```java
+package course;
+
+import java.lang.reflect.Constructor;
+
+public class ReflectCreateObjectTest {
+    public static void main(String[] args) throws Exception {
+        // ① 方式三:Class.forName("全类名") 获取 Student 的 Class 对象
+        // TODO 1: Class<?> clazz = Class.forName("???");
+        Class<?> clazz = Class.forName("course.Student");
+
+        // ② 获取 public 无参构造方法对象
+        // TODO 2: Constructor<?> constructor = clazz.???();
+        Constructor<?> constructor = clazz.getConstructor();
+
+        // ③ 通过构造方法对象 反射创建 Student 对象
+        // TODO 3: Object obj = constructor.???();
+        Object obj = constructor.newInstance();
+
+        // ④ instanceof 判断创建的对象是不是 Student 类型
+        System.out.println("这个对象是 Student 类型吗?" + (obj instanceof Student));
+
+        // ⑤ 强转成 Student,调用方法
+        // TODO 4: 强转 + s.study()
+        if (obj instanceof Student) {
+            Student s = (Student) obj;
+            s.study();
+        }
+    }
+}
+```
+
+**输出示例**:
+```
+这个对象是 Student 类型吗?true
+null在学习.....   ← study() 打印 name+"在学习.....",无参构造 name 默认 null
+```
+
+**选做加分**:
+- 分别用三种方式获取 Class 对象(`Student.class` / `new Student().getClass()` / `Class.forName("course.Student")`),打印三个对象是否相等(`==` 结果应为 true)
+- 用 `clazz.getConstructor(Integer.class, String.class, Integer.class, String.class)` 获取 public 四参构造,`newInstance(1001, "张三", 20, "男")` 创建带属性对象
+- 对比 `new Student()` 与反射创建对象,说说"普通方式代码写死、反射方式动态"的含义
+
+**思考题**:
+1. 为什么 `newInstance()` 返回的是 `Object` 类型,还要强转才能调用 `study()`?
+2. 反射创建对象对应普通调用的哪个动作?(提示:`new 类名()`)
+3. `Class.forName("course.Student")` 会触发 Student 的静态代码块吗?(提示:会!回顾第 10 节类加载时机差异)
+
+---
+
+## 练习二:反射给私有属性赋值 —— Field 字段反射 + 暴力反射
+
+**难度**:⭐⭐⭐⭐
+**知识点**:`getDeclaredField("属性名")` 获取私有字段、`setAccessible(true)` 暴力反射、`field.set(对象, 值)` 赋值、`field.get(对象)` 读取、`Field` 反射核心类
+
+**场景描述**:课堂 Demo06 学了用反射获取**构造方法**,反射三大核心成员还有 **Field(字段属性)**——套路完全一致:**getDeclaredField 拿私有字段 → setAccessible(true) 放开权限 → set 赋值 / get 读取**。本例用反射给 Student 的**私有属性** id/name/age/gender 赋值,再读取出来打印——体会"反射可以操作 private 私有成员"(这也是反射破坏封装性的体现)。
+
+### 案例需求
+
+用反射创建 Student 对象后,通过 Field 反射给 4 个私有属性赋值并读取验证。
+
+### 实现步骤
+
+1. `Class.forName("course.Student")` 获取 Class 对象 + `getConstructor().newInstance()` 创建对象
+2. 用 `clazz.getDeclaredField("name")` 获取私有属性 name 的 Field 对象
+3. `field.setAccessible(true)` 放开私有访问权限(暴力反射)
+4. `field.set(obj, "张三")` 给私有属性赋值
+5. 依次给 id、age、gender 三个属性赋值
+6. 用 `field.get(obj)` 读取属性值打印验证,并调用 study() 观察输出
+
+### 代码框架
+
+```java
+package course;
+
+import java.lang.reflect.Field;
+
+public class ReflectSetFieldTest {
+    public static void main(String[] args) throws Exception {
+        // ① 获取 Class 对象 + 反射创建对象(复用练习一的四步链)
+        Class<?> clazz = Class.forName("course.Student");
+        Object obj = clazz.getConstructor().newInstance();
+
+        // ② 获取私有属性 name 的 Field 对象
+        // TODO 1: Field fieldName = clazz.getDeclaredField("???");
+        Field fieldName = clazz.getDeclaredField("name");
+
+        // ③ 放开私有访问权限(暴力反射的关键一步!)
+        fieldName.setAccessible(true);   // TODO 2: 解释这行代码的作用
+
+        // ④ 给私有属性 name 赋值
+        fieldName.set(obj, "张三");      // TODO 3: field.set(对象, 值) 的含义
+
+        // TODO 4: 依次给 id(1001)、age(20)、gender("男") 赋值
+        Field fieldId = clazz.getDeclaredField("id");
+        fieldId.setAccessible(true);
+        fieldId.set(obj, 1001);
+
+        Field fieldAge = clazz.getDeclaredField("age");
+        fieldAge.setAccessible(true);
+        fieldAge.set(obj, 20);
+
+        Field fieldGender = clazz.getDeclaredField("gender");
+        fieldGender.setAccessible(true);
+        fieldGender.set(obj, "男");
+
+        // ⑤ 用 field.get(obj) 读取属性值打印验证
+        System.out.println("id = " + fieldId.get(obj));
+        System.out.println("name = " + fieldName.get(obj));
+        System.out.println("age = " + fieldAge.get(obj));
+        System.out.println("gender = " + fieldGender.get(obj));
+
+        // ⑥ 调用方法观察输出
+        ((Student) obj).study();
+    }
+}
+```
+
+**输出示例**:
+```
+id = 1001
+name = 张三
+age = 20
+gender = 男
+张三在学习.....   ← 反射赋值后的 name 生效,study() 输出
+```
+
+**选做加分**:
+- 试试**不加** `setAccessible(true)` 直接 `field.set(obj, "张三")`,运行观察抛 **IllegalAccessException**——体会"不放开权限无法操作私有成员"
+- 用 `fieldName.setAccessible(true)` 后调用 `fieldName.getType()` / `fieldName.getName()` 打印字段的类型名和名字
+- 思考:`getField("name")`(不带 Declared)能拿到 private 字段吗?(提示:不能!口诀"含 Declared 的才能拿私有")
+
+**思考题**:
+1. `setAccessible(true)` 做了什么?为什么叫"暴力反射"?
+2. `field.set(obj, 值)` 的三个要素分别是什么?(提示:操作哪个对象、哪个属性、赋什么值)
+3. 用反射给私有属性赋值,和正常用 setter 方法赋值有什么区别?反射有什么隐患?(提示:破坏封装性、效率低 3~100 倍)
+
+---
+
+## 练习三:反射调用方法 —— Method 方法反射 + 带参/返回值
+
+**难度**:⭐⭐⭐⭐
+**知识点**:`getMethod("方法名", 参数类型...)` 获取方法、`getDeclaredMethod` 获取私有方法、`method.invoke(对象, 参数...)` 调用方法、反射获取返回值
+
+**场景描述**:反射三大核心成员的最后一位是 **Method(方法)**——用 `getMethod("方法名", 参数类型...)` 拿到方法对象,再用 **`invoke(对象, 参数...)`** 动态调用方法(对应普通调用的 `对象.方法名(参数)`)。本例用反射调用 Student 的**无参方法 study()** 和**带参数带返回值的方法 sayHello()**——体会反射调用方法时"参数怎么传、返回值怎么拿"。
+
+### 案例需求
+
+用反射调用 Student 的无参方法 study() 和带参带返回值方法 sayHello(String),并打印返回值。
+
+### 实现步骤
+
+1. 创建 Student 对象并反射设置 name="张三"(复用练习二代码)
+2. 用 `clazz.getMethod("study")` 获取 study 方法对象
+3. 用 `studyMethod.invoke(obj)` 反射调用无参方法
+4. 用 `clazz.getMethod("sayHello", String.class)` 获取带 String 参数的方法
+5. 用 `sayHelloMethod.invoke(obj, "李四")` 传入参数调用,接收返回值
+6. 打印反射调用返回的结果
+
+### 代码框架
+
+```java
+package course;
+
+import java.lang.reflect.Method;
+
+public class ReflectInvokeMethodTest {
+    public static void main(String[] args) throws Exception {
+        // ① 反射创建对象 + 反射给 name 赋值(复用练习二)
+        Class<?> clazz = Class.forName("course.Student");
+        Object obj = clazz.getConstructor().newInstance();
+        java.lang.reflect.Field fieldName = clazz.getDeclaredField("name");
+        fieldName.setAccessible(true);
+        fieldName.set(obj, "张三");
+
+        // ② 获取 study 方法对象(无参数)
+        // TODO 1: Method studyMethod = clazz.getMethod("???");
+        Method studyMethod = clazz.getMethod("study");
+
+        // ③ 反射调用无参方法
+        studyMethod.invoke(obj);   // TODO 2: method.invoke(对象) 的含义
+
+        // ④ 获取带 String 参数的 sayHello 方法
+        // TODO 3: Method sayHelloMethod = clazz.getMethod("sayHello", ???);
+        Method sayHelloMethod = clazz.getMethod("sayHello", String.class);
+
+        // ⑤ 反射调用带参方法,接收返回值
+        // TODO 4: Object result = sayHelloMethod.invoke(对象, 参数);
+        Object result = sayHelloMethod.invoke(obj, "李四");
+
+        // ⑥ 打印返回值
+        System.out.println("sayHello 返回:" + result);
+    }
+}
+```
+
+**Student 实体类补充方法**(练习需要的实体):
+
+```java
+// 在 Student 类中补充 sayHello 方法(已有 study() 方法)
+public String sayHello(String name) {
+    return "你好," + name + "!我是" + this.name;
+}
+```
+
+**输出示例**:
+```
+张三在学习.....              ← 反射调用 study()
+sayHello 返回:你好,李四!我是张三   ← 反射调用 sayHello("李四") 的返回值
+```
+
+**选做加分**:
+- 用 `getDeclaredMethod("study")` 并注释 `setAccessible(true)`,观察调用 `invoke` 时抛的异常
+- 尝试用反射调用一个**私有方法**(在 Student 中加一个 `private String getInfo()`),体会 getDeclaredMethod + setAccessible(true) 的完整套路
+- 打印 `method.getParameterCount()` / `method.getReturnType()` 观察方法的参数个数和返回类型
+
+**思考题**:
+1. `invoke(obj, 参数...)` 的第一个参数是对象,那调用**静态方法**时第一个参数传什么?(提示:传 null 即可)
+2. `getMethod` 和 `getDeclaredMethod` 的区别是什么?(提示:是否包含私有方法,口诀同上)
+3. 反射调用带参方法,返回值怎么接收?(提示:`invoke` 返回 `Object`,可强转)
+
+---
+
+## 练习四:综合实战 —— 配置文件驱动 + 反射创建对象 + 赋值 + 调方法(模拟框架底层)
+
+**难度**:⭐⭐⭐⭐⭐
+**知识点**:`Properties` 加载配置文件(Demo03 学过的 getResourceAsStream / load / getProperty)、`Class.forName` 动态加载类、反射 Constructor + Field + Method 全家桶、配置与代码分离思想
+
+**场景描述**:这是"为什么需要反射"第 ① 个场景**配置驱动**的完整落地——**框架读取配置文件里字符串类型的类名,运行时通过反射把它翻译成真实类、创建对象、设置属性、调用方法**(Spring IOC 的简化版)。本练习把"**配置类名 / 配置属性 / 配置方法名**"全部写在 `student.properties` 配置文件里,代码只写"读配置 + 反射"的通用逻辑——**换一个类不用改代码,只改配置文件**,深刻体会配置驱动 + 反射的解耦威力。
+
+### 案例需求
+
+读取配置文件 student.properties,用反射完成:加载类 → 创建对象 → 给属性赋值 → 调用方法,全程不写死具体类名。
+
+### 实现步骤
+
+1. 创建配置文件 `student.properties`,写入类名、属性、方法名
+2. 用 `ClassLoader.getSystemClassLoader().getResourceAsStream("student.properties")` 加载配置(回顾 Demo03)
+3. `Properties` 的 `load()` 解析 + `getProperty()` 读取配置
+4. `Class.forName(配置里的类名)` 反射加载类
+5. `getConstructor().newInstance()` 反射创建对象
+6. 反射给 name / age 等属性赋值(从配置文件读值)
+7. `getMethod(配置里的方法名).invoke(obj)` 反射调用方法
+
+### 代码框架
+
+```java
+package course;
+
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Properties;
+
+public class ReflectFrameWorkTest {
+    public static void main(String[] args) throws Exception {
+        // ① 加载配置文件(回顾 Demo03:类加载器加载资源文件)
+        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
+        InputStream is = classLoader.getResourceAsStream("student.properties");
+        Properties pro = new Properties();
+        pro.load(is);
+        is.close();
+
+        // ② 从配置读取:类名 / 属性名+属性值 / 方法名(TODO 全部从配置文件读取,不要写死!)
+        String className = pro.getProperty("student.className");   // TODO 1: 读类名
+        String name = pro.getProperty("student.name");             // TODO 2: 读属性值
+        String methodName = pro.getProperty("student.methodName"); // TODO 3: 读方法名
+
+        // ③ 反射:根据配置的类名加载类 + 创建对象(框架的核心动作!)
+        Class<?> clazz = Class.forName(className);        // TODO 4: 用配置的类名反射加载
+        Object obj = clazz.getConstructor().newInstance();
+
+        // ④ 反射:给属性赋值(属性名、属性值都来自配置文件)
+        Field fieldName = clazz.getDeclaredField("name");
+        fieldName.setAccessible(true);
+        fieldName.set(obj, name);
+
+        // ⑤ 反射:调用方法(方法名来自配置文件)
+        Method method = clazz.getMethod(methodName);      // TODO 5: 用配置的方法名获取
+        method.invoke(obj);
+
+        // ⑥ 打印创建的对象,验证属性已赋值
+        System.out.println(obj);
+    }
+}
+```
+
+**配置文件 student.properties**:
+
+```properties
+# 来源:src/student.properties
+# 模拟框架的"配置驱动":类名 / 属性 / 方法名全部由配置决定
+student.className=course.Student
+student.name=配置驱动小明
+student.methodName=study
+```
+
+**Student 实体类**(本练习使用,含 toString):
+
+```java
+package course;
+
+public class Student {
+    private Integer id;
+    private String name;
+    private Integer age;
+    private String gender;
+
+    public Student() {}
+
+    public void study() {
+        System.out.println(name + "正在学习Java反射.........");
+    }
+
+    @Override
+    public String toString() {
+        return "Student{name='" + name + "'}";
+    }
+}
+```
+
+**输出示例**:
+```
+配置驱动小明正在学习Java反射.........
+Student{name='配置驱动小明'}
+```
+
+**选做加分**:
+- **换类不改代码**:把 `student.properties` 里改成另一个类的类名(如一个 `Teacher` 类,含 name 属性和 teach() 方法),代码**一行不改**直接运行——体会"配置驱动 + 反射解耦"的威力
+- 给配置文件再加一个属性(如 `student.age=20`),在代码中反射给 age 赋值并打印
+- 把方法名配置改成 `sayHello`(带参方法),思考带参方法如何从配置传参、用 `getMethod(methodName, String.class)` + `invoke(obj, 参数)` 调用
+
+**思考题**:
+1. 如果 `student.properties` 里的类名写错了,程序会抛什么异常?(提示:ClassNotFoundException,回顾 Demo05)
+2. 为什么框架要用"配置文件写类名 + 反射加载",而不是直接 `new 类名()`?(提示:代码编译期不知道类名,配置驱动 + 解耦)
+3. 这套"读配置 → Class.forName → 反射创建/赋值/调用"的套路,和 Spring IOC 容器创建 Bean 的过程有什么相似之处?
+
+---
+
+# 涵盖知识点总览
+
+| 知识点 | 说明 |
+|--------|------|
+| `Class.forName("全类名")` | 方式三获取 Class 对象:字符串传类名、最灵活、框架最常用;全类名写错抛 ClassNotFoundException |
+| 获取 Class 对象三种方式 | ①类名.class(编译期类型检查最安全)②对象.getClass()(需有对象实例)③Class.forName(最灵活)——同一个类三种方式拿到同一个对象 |
+| `getConstructor()` / `newInstance()` | 获取 public 无参构造 + 反射创建对象(对应普通 `new 类名()`) |
+| `getConstructor(参数类型...)` | 获取指定参数类型的 public 构造方法,`newInstance(参数...)` 创建带属性对象 |
+| `getDeclaredField("属性名")` | 获取字段 Field 对象(**含 private**);不带 Declared 的 `getField` 只能拿 public |
+| `field.setAccessible(true)` | 暴力反射:放开私有成员的访问权限,否则操作 private 会抛 IllegalAccessException |
+| `field.set(对象, 值)` / `field.get(对象)` | 反射给属性赋值 / 读取属性值——对应普通调用的 setter / getter |
+| `getMethod("方法名", 参数类型...)` | 获取方法 Method 对象;带参方法要传参数类型,如 `getMethod("sayHello", String.class)` |
+| `method.invoke(对象, 参数...)` | 反射调用方法(对应普通调用 `对象.方法名(参数)`);返回 Object 可强转,调用静态方法传 null |
+| 反射创建对象四步链 | ①Class.forName 拿 Class → ②getConstructor 拿构造 → ③newInstance 建对象 → ④instanceof 判断 + 强转 + 调用方法 |
+| 反射三大核心成员 | Constructor(构造方法创建对象)/ Field(字段属性读写)/ Method(方法调用)——套路一致:Declared 拿私有 + setAccessible(true) 放开权限 |
+| 配置驱动 + 反射 | 框架读取配置文件里的类名字符串 → Class.forName → 反射创建/赋值/调用——换类不改代码,解耦(Spring IOC 底层原理) |
+| `Properties` 加载配置文件 | `getResourceAsStream("xxx.properties")` + `load()` 解析 + `getProperty()` 取值(回顾 Demo03) |
+| instanceof + 向下转型 | 反射创建的对象是 Object 类型,`instanceof Student` 判断后强转再调用方法 |
+| 反射的注意事项 | 日常业务尽量不用:破坏封装性、效率比正常创建对象低 3~100 倍;学习反射为理解框架底层源码 |
+| 反射 API 的受检异常 | ClassNotFoundException / NoSuchMethodException / InstantiationException / IllegalAccessException / InvocationTargetException——必须处理(main 用 throws) |