说明:练习今日新学知识点——反射机制(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)的后两者。
难度:⭐⭐⭐
知识点:获取 Class 对象三种方式(类名.class / 对象.getClass() / Class.forName("全类名"),同一个类三种方式拿到同一个对象)、getConstructors() / getDeclaredConstructors() 获取构造方法数组、getConstructor(参数类型...) / getDeclaredConstructor(参数类型...) 获取指定构造方法、newInstance() 反射创建对象、setAccessible(true) 暴力反射访问私有构造、instanceof 判断类型、向下转型、调用 toString()
场景描述:课堂学了反射最核心的用途——拿到 Class 对象后,通过构造方法创建对象(这是 Spring IOC 等框架的底层原理)。SysUser 系统用户类一共有 4 个构造方法(2 个 public + 2 个 private):
// 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) 暴力反射才能调用。
💡 获取 Class 对象的方式:第一步"拿 Class"有三种方式任选其一,结果殊途同归(同一个类三种方式拿到的是同一个对象)—— ①
SysUser.class(类名.class,编译期类型检查最安全) ②new SysUser().getClass()(对象.getClass(),需先有对象实例) ③Class.forName("course.SysUser")(全类名字符串,最灵活、框架最常用)
用反射分别通过 SysUser 的 4 个构造方法创建 4 个对象,并打印每个对象验证(toString())。
SysUser.class / new SysUser().getClass() / Class.forName("course.SysUser"))getConstructors() 统计 public 构造个数(2 个),getDeclaredConstructors() 统计全部构造个数(4 个,含 private)getConstructor() 获取 public 无参构造 → newInstance() 创建空对象getConstructor(String.class × 6) 获取 public 六参构造 → newInstance(id, username, password, name, phone, email) 创建完整用户对象getDeclaredConstructor(String.class, String.class) 获取 private 二参构造 → setAccessible(true) → newInstance("admin", "admin888") 暴力反射创建对象getDeclaredConstructor(String.class × 4) 获取 private 四参构造 → setAccessible(true) → newInstance(id, name, phone, email) 暴力反射创建对象instanceof SysUser 判断 + 打印 toString() 验证属性package course;
import java.lang.reflect.Constructor;
public class ReflectCreateSysUserTest {
public static void main(String[] args) throws Exception {
// 1. 获取 SysUser 的 Class 对象(类.class / 对象.getClass() / Class.forName 三选一)
// 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——体会"私有构造必须放开访问权限"getConstructor(String.class, String.class)(不带 Declared)尝试获取 private 二参构造,观察抛 NoSuchMethodException——验证"不含 Declared 只能拿 public"for (Constructor<?> c : clazz.getDeclaredConstructors()) 打印每个构造的 getParameterCount()(参数个数)和 getParameterTypes()(参数类型),观察 4 个构造的参数差异SysUser.class / new SysUser().getClass() / Class.forName("course.SysUser")),打印三个对象是否相等(== 结果应为 true)思考题:
getConstructor() 拿不到它们?必须用什么方法组合?(提示:getDeclaredConstructor + setAccessible(true),口诀"含 Declared 的才能拿私有")setAccessible(true) 做了什么?Object 类型,怎么确认它确实是 SysUser?确认后如何调用 SysUser 的方法?(提示:instanceof + 强转)类名.class / 对象.getClass() / Class.forName)有什么区别?哪种最灵活、框架最常用?(提示:类名.class 编译期类型检查最安全 / getClass() 需先有对象实例 / Class.forName 字符串传类名最灵活,是框架最常用的方式)难度:⭐⭐⭐⭐
知识点:getDeclaredField("属性名") 获取私有字段、setAccessible(true) 暴力反射、field.set(对象, 值) 赋值、field.get(对象) 读取、Field 反射核心类
场景描述:课堂 Demo06 学了用反射获取构造方法,反射三大核心成员还有 Field(字段属性)——套路完全一致:getDeclaredField 拿私有字段 → setAccessible(true) 放开权限 → set 赋值 / get 读取。本例用反射给 Student 的私有属性 id/name/age/gender 赋值,再读取出来打印——体会"反射可以操作 private 私有成员"(这也是反射破坏封装性的体现)。
用反射创建 Student 对象后,通过 Field 反射给 4 个私有属性赋值并读取验证。
Class.forName("course.Student") 获取 Class 对象 + getConstructor().newInstance() 创建对象clazz.getDeclaredField("name") 获取私有属性 name 的 Field 对象field.setAccessible(true) 放开私有访问权限(暴力反射)field.set(obj, "张三") 给私有属性赋值field.get(obj) 读取属性值打印验证,并调用 study() 观察输出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 的才能拿私有")思考题:
setAccessible(true) 做了什么?为什么叫"暴力反射"?field.set(obj, 值) 的三个要素分别是什么?(提示:操作哪个对象、哪个属性、赋什么值)难度:⭐⭐⭐⭐
知识点:getMethod("方法名", 参数类型...) 获取方法、getDeclaredMethod 获取私有方法、method.invoke(对象, 参数...) 调用方法、反射获取返回值
场景描述:反射三大核心成员的最后一位是 Method(方法)——用 getMethod("方法名", 参数类型...) 拿到方法对象,再用 invoke(对象, 参数...) 动态调用方法(对应普通调用的 对象.方法名(参数))。本例用反射调用 Student 的无参方法 study() 和带参数带返回值的方法 sayHello()——体会反射调用方法时"参数怎么传、返回值怎么拿"。
用反射调用 Student 的无参方法 study() 和带参带返回值方法 sayHello(String),并打印返回值。
clazz.getMethod("study") 获取 study 方法对象studyMethod.invoke(obj) 反射调用无参方法clazz.getMethod("sayHello", String.class) 获取带 String 参数的方法sayHelloMethod.invoke(obj, "李四") 传入参数调用,接收返回值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 实体类补充方法(练习需要的实体):
// 在 Student 类中补充 sayHello 方法(已有 study() 方法)
public String sayHello(String name) {
return "你好," + name + "!我是" + this.name;
}
输出示例:
张三在学习..... ← 反射调用 study()
sayHello 返回:你好,李四!我是张三 ← 反射调用 sayHello("李四") 的返回值
选做加分:
getDeclaredMethod("study") 并注释 setAccessible(true),观察调用 invoke 时抛的异常private String getInfo()),体会 getDeclaredMethod + setAccessible(true) 的完整套路method.getParameterCount() / method.getReturnType() 观察方法的参数个数和返回类型思考题:
invoke(obj, 参数...) 的第一个参数是对象,那调用静态方法时第一个参数传什么?(提示:传 null 即可)getMethod 和 getDeclaredMethod 的区别是什么?(提示:是否包含私有方法,口诀同上)invoke 返回 Object,可强转)难度:⭐⭐⭐⭐⭐
知识点:Properties 加载配置文件(Demo03 学过的 getResourceAsStream / load / getProperty)、Class.forName 动态加载类、反射 Constructor + Field + Method 全家桶、配置与代码分离思想
场景描述:这是"为什么需要反射"第 ① 个场景配置驱动的完整落地——框架读取配置文件里字符串类型的类名,运行时通过反射把它翻译成真实类、创建对象、设置属性、调用方法(Spring IOC 的简化版)。本练习把"配置类名 / 配置属性 / 配置方法名"全部写在 student.properties 配置文件里,代码只写"读配置 + 反射"的通用逻辑——换一个类不用改代码,只改配置文件,深刻体会配置驱动 + 反射的解耦威力。
读取配置文件 student.properties,用反射完成:加载类 → 创建对象 → 给属性赋值 → 调用方法,全程不写死具体类名。
student.properties,写入类名、属性、方法名ClassLoader.getSystemClassLoader().getResourceAsStream("student.properties") 加载配置(回顾 Demo03)Properties 的 load() 解析 + getProperty() 读取配置Class.forName(配置里的类名) 反射加载类getConstructor().newInstance() 反射创建对象getMethod(配置里的方法名).invoke(obj) 反射调用方法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:
# 来源:src/student.properties
# 模拟框架的"配置驱动":类名 / 属性 / 方法名全部由配置决定
student.className=course.Student
student.name=配置驱动小明
student.methodName=study
Student 实体类(本练习使用,含 toString):
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, 参数) 调用思考题:
student.properties 里的类名写错了,程序会抛什么异常?(提示:ClassNotFoundException,回顾 Demo05)new 类名()?(提示:代码编译期不知道类名,配置驱动 + 解耦)| 知识点 | 说明 |
|---|---|
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) |