20260812-反射练习.md 20 KB

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 对象三种方式(类名.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())。

实现步骤

  1. 获取 SysUser 的 Class 对象(三种方式任选其一:SysUser.class / new SysUser().getClass() / Class.forName("course.SysUser"))
  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() 验证属性

代码框架

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——体会"私有构造必须放开访问权限"
  • 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 + 强转)
  4. 获取 Class 对象的三种方式(类名.class / 对象.getClass() / Class.forName)有什么区别?哪种最灵活、框架最常用?(提示:类名.class 编译期类型检查最安全 / getClass() 需先有对象实例 / Class.forName 字符串传类名最灵活,是框架最常用的方式)

练习二:反射给私有属性赋值 —— 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() 观察输出

代码框架

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. 打印反射调用返回的结果

代码框架

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 时抛的异常
  • 尝试用反射调用一个私有方法(在 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) 反射调用方法

代码框架

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, 参数) 调用

思考题:

  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)