# 2026-08-12 反射练习(课堂练习用) > **说明**:练习今日新学知识点——**反射机制(Reflection)**(Demo04:反射概念、普通调用 vs 反射调用、为什么需要反射、反射核心类)、**获取 Class 对象三种方式**(Demo05:类名.class / 对象.getClass() / Class.forName("全类名"),同一个类三种方式拿到同一个对象)、**反射 Constructor 构造方法**(Demo06:getConstructors / getConstructor / getDeclaredConstructors / getDeclaredConstructor 获取构造方法、setAccessible(true) 暴力反射、newInstance() 反射创建对象、instanceof 判断 + 强转调用方法)。练习一用反射**分别通过 SysUser 的 4 个构造方法**(public 无参 / private 二参 / public 六参 / private 四参)创建对象;练习二、三、四延伸使用 **Field 字段反射**(给私有属性赋值)与 **Method 方法反射**(调用私有/带参方法)——它们是反射三大核心成员(Constructor / Field / Method)的后两者。 --- ## 练习一:反射创建对象 —— 遍历 SysUser 的四个构造方法建对象 **难度**:⭐⭐⭐ **知识点**:`Class.forName("全类名")` 获取 Class 对象、`getConstructors()` / `getDeclaredConstructors()` 获取构造方法数组、`getConstructor(参数类型...)` / `getDeclaredConstructor(参数类型...)` 获取指定构造方法、`newInstance()` 反射创建对象、`setAccessible(true)` 暴力反射访问私有构造、`instanceof` 判断类型、向下转型、调用 toString() **场景描述**:课堂学了**反射最核心的用途**——拿到 Class 对象后,通过构造方法创建对象(这是 Spring IOC 等框架的底层原理)。`SysUser` 系统用户类一共有 **4 个构造方法**(2 个 public + 2 个 private): ```java // SysUser 的 4 个构造方法(源码:course/SysUser.java) public SysUser() // ① public 无参 private SysUser(String username, String password) // ② private 二参(用户名,密码) public SysUser(String id, String username, String password, String name, String phone, String email) // ③ public 六参(完整用户信息) private SysUser(String id, String name, String phone, String email) // ④ private 四参(基础信息) ``` 本例用反射**分别通过这 4 个构造方法创建 4 个 SysUser 对象**——完整走一遍"拿 Class → 拿构造 → 建对象"的反射创建对象流程,并重点体会:**private 构造方法必须用 `getDeclaredConstructor` + `setAccessible(true)` 暴力反射**才能调用。 ### 案例需求 用反射分别通过 SysUser 的 4 个构造方法创建 4 个对象,并打印每个对象验证(`toString()`)。 ### 实现步骤 1. `Class.forName("course.SysUser")` 获取 SysUser 的 Class 对象 2. `getConstructors()` 统计 public 构造个数(2 个),`getDeclaredConstructors()` 统计全部构造个数(4 个,含 private) 3. **反射创建对象①**:`getConstructor()` 获取 public 无参构造 → `newInstance()` 创建空对象 4. **反射创建对象②**:`getConstructor(String.class × 6)` 获取 public 六参构造 → `newInstance(id, username, password, name, phone, email)` 创建完整用户对象 5. **反射创建对象③**:`getDeclaredConstructor(String.class, String.class)` 获取 private 二参构造 → `setAccessible(true)` → `newInstance("admin", "admin888")` 暴力反射创建对象 6. **反射创建对象④**:`getDeclaredConstructor(String.class × 4)` 获取 private 四参构造 → `setAccessible(true)` → `newInstance(id, name, phone, email)` 暴力反射创建对象 7. 每个对象 `instanceof SysUser` 判断 + 打印 `toString()` 验证属性 ### 代码框架 ```java package course; import java.lang.reflect.Constructor; public class ReflectCreateSysUserTest { public static void main(String[] args) throws Exception { // 1. 用 Class.forName 获取 SysUser 的 Class 对象 // 2. 分别打印 public 构造个数和全部构造个数 // 3. 用 public 无参构造反射创建对象①,并 instanceof 判断 // 4. 用 public 六参构造反射创建对象② // 5. 用 private 二参构造反射创建对象③(需放开访问权限) // 6. 用 private 四参构造反射创建对象④(需放开访问权限) } } ``` **输出示例**: ``` public 构造方法个数:2 全部构造方法个数(含private):4 ① 无参构造:SysUser{id='null', username='null', password='null', name='null', phone='null', email='null'}(是SysUser吗?true) ② 六参构造:SysUser{id='1', username='zhangsan', password='123456', name='张三', phone='13800000000', email='zhangsan@qq.com'} ③ 私有二参构造:SysUser{id='null', username='admin', password='admin888', name='null', phone='null', email='null'} ④ 私有四参构造:SysUser{id='2', username='null', password='null', name='李四', phone='13900000000', email='lisi@qq.com'} ``` **选做加分**: - **暴力反射对比实验**:把对象③/④的 `setAccessible(true)` 注释掉再运行,观察抛 **IllegalAccessException**——体会"私有构造必须放开访问权限" - **Declared 口诀验证**:用 `getConstructor(String.class, String.class)`(不带 Declared)尝试获取 private 二参构造,观察抛 **NoSuchMethodException**——验证"不含 Declared 只能拿 public" - **遍历构造方法**:`for (Constructor c : clazz.getDeclaredConstructors())` 打印每个构造的 `getParameterCount()`(参数个数)和 `getParameterTypes()`(参数类型),观察 4 个构造的参数差异 - 分别用三种方式获取 Class 对象(`SysUser.class` / `new SysUser().getClass()` / `Class.forName("course.SysUser")`),打印三个对象是否相等(`==` 结果应为 true) **思考题**: 1. SysUser 有 2 个 private 构造方法,为什么 `getConstructor()` 拿不到它们?必须用什么方法组合?(提示:`getDeclaredConstructor` + `setAccessible(true)`,口诀"含 Declared 的才能拿私有") 2. public 构造和 private 构造,反射创建对象的方式有什么不同?多出来的 `setAccessible(true)` 做了什么? 3. 反射创建的对象是 `Object` 类型,怎么确认它确实是 SysUser?确认后如何调用 SysUser 的方法?(提示:`instanceof` + 强转) --- ## 练习二:反射给私有属性赋值 —— 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 { // 1. 反射创建 Student 对象(复用练习一的流程) // 2. 获取私有属性 name 的字段对象 // 3. 放开私有访问权限 // 4. 给 name 赋值 // 5. 依次给 id、age、gender 赋值 // 6. 读取 4 个属性值打印验证 // 7. 强转成 Student 调用 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 { // 1. 反射创建 Student 对象,并给 name 属性赋值 // 2. 获取无参方法 study 的方法对象 // 3. 反射调用 study 方法 // 4. 获取带 String 参数的 sayHello 方法对象 // 5. 反射调用 sayHello 方法,传入参数并接收返回值 // 6. 打印返回值 } } ``` **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 { // 1. 加载配置文件 student.properties(类加载器 + Properties) // 2. 从配置读取类名、属性值、方法名 // 3. 根据配置的类名反射加载类并创建对象 // 4. 反射给属性赋值 // 5. 反射调用方法 // 6. 打印对象验证 } } ``` **配置文件 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(参数...)` 创建带属性对象 | | 遍历类的多个构造方法创建对象 | SysUser 有 4 个构造方法(2 public + 2 private):`getConstructors()`/`getConstructor` 创建 public 构造对象、`getDeclaredConstructors()`/`getDeclaredConstructor` + `setAccessible(true)` 暴力反射创建 private 构造对象,`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 SysUser`(或 Student)判断后强转再调用方法 | | 反射的注意事项 | 日常业务尽量不用:破坏封装性、效率比正常创建对象低 3~100 倍;学习反射为理解框架底层源码 | | 反射 API 的受检异常 | ClassNotFoundException / NoSuchMethodException / InstantiationException / IllegalAccessException / InvocationTargetException——必须处理(main 用 throws) |