说明:练习今日新学知识点——反射机制(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.forName("全类名") 获取 Class 对象、getConstructor() 获取 public 无参构造、newInstance() 反射创建对象、instanceof 判断类型、向下转型、调用方法
场景描述:课堂学了反射最核心的用途——拿到 Class 对象后,通过构造方法创建对象(这是 Spring IOC 等框架的底层原理)。本例完整走一遍反射创建对象的四步链:① Class.forName 拿 Class → ② getConstructor 拿构造 → ③ newInstance 建对象 → ④ instanceof 判断 + 强转 + 调用方法,与普通 new Student() 做对比。
用反射的方式创建 Student 对象,并调用它的 study() 方法。
Class.forName("course.Student") 获取 Student 的 Class 对象clazz.getConstructor() 获取 public 无参构造方法对象constructor.newInstance() 反射创建 Student 对象obj instanceof Student 判断创建的对象是不是 Student 类型study() 方法new Student() 有什么不同?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
选做加分:
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() 与反射创建对象,说说"普通方式代码写死、反射方式动态"的含义思考题:
newInstance() 返回的是 Object 类型,还要强转才能调用 study()?new 类名())Class.forName("course.Student") 会触发 Student 的静态代码块吗?(提示:会!回顾第 10 节类加载时机差异)难度:⭐⭐⭐⭐
知识点: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 {
// ① 获取 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 的才能拿私有")思考题:
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 {
// ① 反射创建对象 + 反射给 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 实体类补充方法(练习需要的实体):
// 在 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 {
// ① 加载配置文件(回顾 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:
# 来源: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(参数...) 创建带属性对象 |
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) |