# 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) |