c260813courseField 对象代表类中的一个成员变量(属性/字段)。反射可以通过 Field 的 get() 读取字段值、set() 修改字段值。字段的反射操作在框架中应用极广:
// 来源:course/Demo01.java(类注释)
/*
Field对象代表类中的一个成员变量。反射可以通过Field的get()读取字段值、set()修改字段值。
这个在很多框架当中使用的都非常的多,比如我们使用框架查询数据库,把数据库的数据一个一个映射到我们自己创建的Java类中,
使用的就是File.set()来进行字段赋值。
*/
要点:
- Field = 成员变量/属性/字段——反射中表示类的属性信息
- get() 读值、set() 赋值——反射操作属性的两个核心方法
- 框架中的典型应用:ORM 框架查询数据库,把数据库查到的数据一个一个映射到 Java 类中,用的就是
Field.set()来进行字段赋值——这是数据库记录 → Java 对象映射(ORM)的底层原理
| 对比 | 普通方式 | 反射方式 |
|---|---|---|
| 读取属性 | 对象.属性名(如 s.email) |
field.get(对象) |
| 修改属性 | 对象.属性名 = 值 |
field.set(对象, 值) |
| 私有属性 | 编译期直接报错(private 不可见) | getDeclaredField() + setAccessible(true) 可访问 |
| 静态属性 | 类名.属性名(如 Student.school) |
field.get(null) / field.set(null, 值) |
要点:
- 普通方式操作属性,私有成员在编译期就无法访问;反射方式可以动态拿到任意属性的 Field 对象(包括 private)
- 反射操作属性是"运行时动态"的——属性名可以是字符串变量/配置文件内容,这正是框架映射数据库字段时"字段名写死在配置里、运行时通过反射赋值"的原理
拿到 Class 对象后,用以下 4 个方法获取 Field 对象(与上一课 Constructor 的 API 套路完全一致):
// 来源:course/Demo01.java(类注释)
/*
Class类的获取Field对象的方法
public Field[] getFields() 返回所有public修饰的字段属性,包括从父类继承的
public Field getField(String name) 返回指定的单个public修饰的字段属性
public Field[] getDeclaredFields() 返回本类(不包含父类)声明的所有的字段属性包括private
public Field getDeclaredField(String name) 返回指定的单个的字段属性(包括private)
*/
| 方法 | 作用 | 是否含 private | 是否含父类继承的字段 |
|---|---|---|---|
getFields() |
返回所有 public 修饰的字段属性数组 | ❌ 只 public | ✅ 包括从父类继承的 |
getField(String name) |
返回指定的单个 public 字段属性 | ❌ 只 public | ✅ 包括从父类继承的 |
getDeclaredFields() |
返回本类声明的所有字段属性数组 | ✅ 含 private | ❌ 不包含父类 |
getDeclaredField(String name) |
返回指定的单个字段属性 | ✅ 含 private | ❌ 不包含父类 |
要点:
- 记忆口诀(与 Constructor 一致):含
Declared的能拿到私有(包含所有),不含Declared的只能拿 publicDeclared(本类声明)的语义:只拿本类里声明的字段,不包含父类;非Declared反而包含从父类继承的 public 字段——这正是getFields()与getDeclaredFields()的核心区别- 参数写法:
getField(String name)/getDeclaredField(String name)传入字段名字符串(如"email"),字段名不存在会抛NoSuchFieldException(受检异常)
// 来源:course/Demo01.java(类注释)
/*
File类的方法
public Object get(Object obj) 读取对象obj的该属性值
public void set(Object obj, Object value) 设置对象obj的该属性值
public void setAccessible(boolean flag) 暴力反射,临时取消访问权限检查
public Class<?> getType() 获取属性的类型
public String getName() 获取属性名
public int getModifiers() 获取属性的修饰符 后面可以使用Modifier解析
*/
| 方法 | 作用 | 说明 |
|---|---|---|
get(Object obj) |
读取对象 obj 的该属性值 | 返回 Object,拿到的是 Object 类型,使用时常需强转 |
set(Object obj, Object value) |
设置对象 obj 的该属性值 | 参数 ①对象 ②要赋的值 |
setAccessible(boolean flag) |
暴力反射:临时取消访问权限检查 | 访问 private 字段前必须调用 setAccessible(true) |
getType() |
获取属性的类型 | 返回 Class<?>,可再调 getSimpleName() 得到类型名 |
getName() |
获取属性名 | 返回字段名字符串 |
getModifiers() |
获取属性的修饰符 | 返回 int 数值,需配合 Modifier.toString() 解析成 public/private/static... |
| 场景 | get 的参数 | set 的参数 | 原因 |
|---|---|---|---|
| 操作实例属性 | field.get(s)(传对象) |
field.set(s, 值)(传对象+值) |
实例属性属于某个对象,必须指明是"哪个对象的属性" |
| 操作静态属性 | field.get(null)(传 null) |
field.set(null, 值)(传 null+值) |
静态属性属于整个类、不属于任何对象,对象参数传 null |
要点:
- 读取/修改属性必须带"目标对象"——因为同一个字段在不同对象上有不同的值,必须告诉反射"操作哪个对象"
- 静态属性传 null:静态成员不属于某个对象、属于整个类,所以对象参数传 null
- getModifiers() 返回的是 int 数字,需要用
java.lang.reflect.Modifier类的toString()方法解析成可读字符串(如private、public static)
反射的第一个典型应用:拿到一个类的所有属性,并逐一输出"修饰符 + 类型 + 属性名"——这在框架底层(打印对象信息、生成 SQL、DTO 校验)非常常用。
// 来源:course/Demo01.java
public class Demo01 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
//获取Class对象
Class<Student> clazz = Student.class;
//1、获取所有属性并且输出修饰符和类型
System.out.println(clazz.getSimpleName()+"类的所有属性:");
//获取Student类型的所有属性,包含私有的,但是不包括父类的属性
Field[] fields = clazz.getDeclaredFields();
for (Field field:fields){
//获取属性的修饰符
int modifiers = field.getModifiers();
String mod = Modifier.toString(modifiers); //public / private/static...
//获取属性的类型
Class<?> type = field.getType();
String typeName = type.getSimpleName();
System.out.println(mod+" "+typeName+" "+field.getName());
}
// ……(第 2~4 部分代码见后续章节)……
}
}
Student类的所有属性:
private Integer id
private String name
private Integer age
private String gender
public String email
public String phone
public static String school
| 代码 | 作用 | 说明 |
|---|---|---|
Student.class |
获取 Class 对象 | 方式一:类名.class(上一课内容,编译期类型检查最安全) |
clazz.getSimpleName() |
获取简单类名 | 输出 Student |
clazz.getDeclaredFields() |
获取本类声明的全部字段(含 private,不含父类) | 拿到 7 个字段的 Field 数组 |
field.getModifiers() |
获取修饰符(int 数值) | 如 private=2、public=1、static=8(JVM 常量值) |
Modifier.toString(modifiers) |
把 int 修饰符解析成可读字符串 | 输出 private / public static 等 |
field.getType() |
获取属性类型 | 返回 Class<?>(如 Integer.class) |
type.getSimpleName() |
获取类型简单名 | 输出 Integer / String |
field.getName() |
获取属性名 | 输出 id / name / school 等 |
要点:
getDeclaredFields()拿的是本类声明的全部字段(含 private),不包括父类——所以 Student 的 7 个字段全部被打印(id/name/age/gender/email/phone/school),而父类 Object 的字段不会被打印getModifiers()返回 int 数值,必须用Modifier.toString()解析——本课新增的java.lang.reflect.Modifier工具类专门负责解析修饰符- 修饰符解析结果示例:
private、public、public static——静态字段 school 输出为public static String school
对于 public 字段,可以用 getField("字段名") 获取 Field 对象,再用 get() 读取、set() 修改:
// 来源:course/Demo01.java
public class Demo01 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
//获取Class对象
Class<Student> clazz = Student.class;
// 2、读取/修改 public 属性
Student s=new Student(1001,"张三",25,"男","zhangsan@163.com","13636363366");
//Class对象根据指定的属性名创建对象
Field emailField = clazz.getField("email");
//读取 s对象的public属性
System.out.println("读取email的值:"+emailField.get(s));
//设置 s 对象的public属性
emailField.set(s,"lisi@qq.com");
System.out.println("修改后的:"+s.email);
}
}
读取email的值:zhangsan@163.com
修改后的:lisi@qq.com
| 代码 | 作用 | 说明 |
|---|---|---|
new Student(1001, "张三", 25, "男", "zhangsan@163.com", "13636363366") |
创建 Student 对象 | 调用 public 六参构造(email、phone 是 public 字段,构造里赋值) |
clazz.getField("email") |
获取指定的 public 字段 | 字段名不存在会抛 NoSuchFieldException |
emailField.get(s) |
读取 s 对象的 email 属性值 | 返回 Object,输出 zhangsan@163.com |
emailField.set(s, "lisi@qq.com") |
修改 s 对象的 email 属性值 | 反射赋值后对象属性直接改变 |
s.email |
直接访问验证 | 反射 set 之后用普通方式 s.email 也能看到新值——反射赋值与普通赋值是同一份数据 |
要点:
- getField() 只能拿 public 字段(含父类继承的 public 字段),拿私有字段会抛异常
- 反射 set 与普通访问是同一份数据:
emailField.set(s, ...)之后直接s.email读取,值已经改变——证明反射操作的就是对象的真实字段内存- get() 返回值类型是 Object:使用时可能需要强转成目标类型(如
String email = (String) emailField.get(s))
private 字段在普通代码中编译期就无法访问,但反射可以拿到它的 Field 对象。直接 get()/set() 会因权限检查报 IllegalAccessException,必须先调用 setAccessible(true) 临时取消访问权限检查——这就是暴力反射。
// 来源:course/Demo01.java
public class Demo01 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
//获取Class对象
Class<Student> clazz = Student.class;
//3、 读取/修改 private属性 -- 暴力反射
Field nameField = clazz.getDeclaredField("name");
//暴力反射--临时取消权限检查
nameField.setAccessible(true);
System.out.println("读取 private name 的值:"+nameField.get(s));
nameField.set(s,"李四");
System.out.println("修改后的 private name 值:"+nameField.get(s));
}
}
读取 private name 的值:张三
修改后的 private name 值:李四
nameField = clazz.getDeclaredField("name") ← 获取 private 字段(getDeclaredField 能拿到)
↓
nameField.get(s) / set(s, "李四") ← 不 setAccessible(true) 直接操作 → 抛 IllegalAccessException
↓
nameField.setAccessible(true) ← 暴力反射:临时取消访问权限检查
↓
get(s) 读取 / set(s, "李四") 修改 ← 权限放行,成功操作 private 字段
| 代码 | 作用 | 说明 |
|---|---|---|
clazz.getDeclaredField("name") |
获取指定的单个字段(包括 private) | 必须用 Declared 系列才能拿到私有字段 |
nameField.setAccessible(true) |
临时取消访问权限检查 | 暴力反射的关键一步,否则对 private 字段 get/set 会报权限错误 |
nameField.get(s) |
读取 private name 属性值 | 输出 张三 |
nameField.set(s, "李四") |
修改 private name 属性值 | 反射修改成功后,再 get 输出 李四 |
要点:
- 获取 private 字段必须用
getDeclaredField()(含 Declared),getField()只能拿 public- 操作 private 字段必须
setAccessible(true)——这是暴力反射,也是上一课"反射可能破坏封装性"的具体体现- 与上一课 Constructor 的套路完全一致:
getDeclaredXxx拿私有 +setAccessible(true)放开权限——反射三大成员(Constructor/Field/Method)的暴力反射操作模式相同
静态成员(静态属性、静态方法、静态代码块)不属于某个对象,而是属于整个类的。所以反射操作静态属性时,get()/set() 的对象参数不传对象,传 null。
// 来源:course/Demo01.java
public class Demo01 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
//获取Class对象
Class<Student> clazz = Student.class;
Student.school="哈尔滨佛学院";
//4、操作静态属性 传null
Field schoolField = clazz.getDeclaredField("school");
//暴力反射--临时取消权限检查
nameField.setAccessible(true);
//如果要获取的是静态属性 就 传null
//静态成员(静态属性、静态方法、静态代码块)不属于某个对象,而是属于整个类的。所以不传对象参数,传null
Object value = schoolField.get(null);
System.out.println("静态属性 school :"+value);
schoolField.set(null,"福建桥梁职业技术学院");
System.out.println("修改后的属性 school :"+Student.school);
}
}
静态属性 school :哈尔滨佛学院
修改后的属性 school :福建桥梁职业技术学院
| 代码 | 作用 | 说明 |
|---|---|---|
Student.school = "哈尔滨佛学院" |
普通方式给静态字段赋值 | 静态字段可用"类名.属性名"直接操作 |
clazz.getDeclaredField("school") |
获取 static 字段的 Field 对象 | school 是 public static 字段,同样用 Declared 系列获取 |
schoolField.get(null) |
读取静态属性——对象参数传 null | 静态成员不属于某个对象、属于整个类,不传对象 |
schoolField.set(null, "福建桥梁职业技术学院") |
修改静态属性——对象参数传 null | set 的第一参数(对象)也传 null |
Student.school |
直接访问验证 | 反射 set 后 Student.school 输出新值——静态属性反射修改成功 |
要点:
- 静态属性对象参数传 null:静态成员属于整个类、不属于任何对象,
get(null)/set(null, 值)——记住这个规律- 反射修改静态属性后,
类名.属性名直接访问也生效:schoolField.set(null, ...)之后Student.school已变为新值——静态字段全类共享一份- private static 字段同样适用:static 与 private 可以叠加(
private static),获取/操作时既要用getDeclaredField也要setAccessible(true)
Student 是本课的反射目标实体类,字段设计刻意覆盖了三种权限类型,正好对应 Demo01 的三段演示:
// 来源:course/Student.java
public class Student{
private Integer id;
private String name;
private Integer age;
private String gender;
public String email;
public String phone;
public static String school;
public Student() {
}
private Student(String name, Integer id) {
this.name = name;
this.id = id;
}
public Student(Integer id, String name, Integer age, Integer gender) {
this.id = id;
this.name = name;
this.age = age;
this.gender = gender;
}
public Student(Integer id, String name, Integer age, Integer gender, String email, String phone) {
this.id = id;
this.name = name;
this.age = age;
this.gender = gender;
this.email = email;
this.phone = phone;
}
// …… 7 组 getter/setter(id/name/age/gender/email/phone/school)见下方补充……
private void read(){
System.out.println(this.name+"读.......");
}
private static void write(){
System.out.println("静态方法...写...............");
}
public void study(){
System.out.println(name+"在学习.....");
}
}
| 字段 | 权限 | 作用 / 对应演示 |
|---|---|---|
private Integer id |
private | 私有字段——反射 getDeclaredFields() 能拿到(演示①) |
private String name |
private | 私有字段——暴力反射 getDeclaredField + setAccessible(true) 读取/修改(演示③) |
private Integer age |
private | 私有字段——演示① 打印修饰符/类型 |
private String gender |
private | 私有字段——演示① 打印修饰符/类型 |
public String email |
public | 公有字段——getField + get/set 读取/修改(演示②) |
public String phone |
public | 公有字段——演示① 打印修饰符/类型 |
public static String school |
public + static | 静态字段——get(null) / set(null, 值) 操作(演示④) |
| 构造方法 | 权限 | 参数 |
|---|---|---|
Student() |
public | 无参 |
Student(String name, Integer id) |
private | 二参(上一课暴力反射目标) |
Student(Integer id, String name, Integer age, Integer gender) |
public | 四参 |
Student(Integer id, String name, Integer age, Integer gender, String email, String phone) |
public | 六参(本课 Demo01 创建对象用) |
Student 类同步更新,新增了每个字段对应的 getter/setter(id/name/age/gender/email/phone/school 共 7 组,school 的 getter/setter 是 static 的),以及 2 个私有方法 read() / write()——它们正是 Demo02 方法反射(Method)的调用目标:
// 来源:course/Student.java(代码更新新增部分)
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
// …… age/gender/email/phone 的 getter/setter 同理 ……
public static String getSchool() { return school; }
public static void setSchool(String school) { Student.school = school; } // 静态 setter 用 类名.静态字段 赋值
private void read(){ // private 实例方法:输出 name+"读......."
System.out.println(this.name+"读.......");
}
private static void write(){ // private 静态方法:输出"静态方法...写..............."
System.out.println("静态方法...写...............");
}
| 新增成员 | 类型 | 作用 / 对应演示 |
|---|---|---|
getId/setId ~ getPhone/setPhone 共 5 组 |
public 实例 getter/setter | Demo02 用 getMethod("getName") / getMethod("setName", String.class) 反射调用 |
getSchool/setSchool |
public static getter/setter | static 修饰的 getter/setter——调用静态方法 invoke 第一个参数传 null |
read() |
private 实例方法 | 暴力反射目标——getDeclaredMethod("read") + setAccessible(true) + invoke(s) |
write() |
private 静态方法 | 暴力反射 + 静态方法目标——getDeclaredMethod("write") + setAccessible(true) + invoke(null) |
要点:
- getter/setter 是反射 Method 最典型的调用对象:
getMethod("setName", String.class)反射调用 setName 等价于s.setName("李四")——框架(MyBatis/Spring)反射调用 setter 给属性赋值、getter 取属性值正是本课的典型应用- private 方法必须暴力反射:
read()/write()是 private,必须getDeclaredMethod拿到 +setAccessible(true)放开权限才能invoke- 静态方法 invoke 传 null:
write()是 static,invoke(null)即可调用(与 Field 操作静态属性传 null 同一规律)要点:
- 字段刻意混合 public / private / static:就是为了让同一份反射代码展示三种操作路径——
getField(public)、getDeclaredField + setAccessible(private)、get/set(null)(static)- 本课用六参构造创建对象:
new Student(1001, "张三", 25, "男", "zhangsan@163.com", "13636363366")——因为 email、phone 是 public 字段,构造里可以给它们赋值- 上节课已学构造方法:4 个构造方法、
study()方法在上一课(08-12 反射 Constructor)已分析,本课重点是字段(Field)这一维度
Method 对象代表类中的一个方法。反射通过 Method.invoke(对象, 参数...) 来调用方法,等价于 对象.方法(参数)——但方法的名称和参数都是运行时才确定的。这是反射三大核心成员(Constructor / Field / Method)的最后一位,也是动态代理、测试框架、注解解释器等高级功能的基础。
// 来源:course/Demo02.java(类注释)
/*
Method对象代表类中的一个方法,反射通过Method.invoke(对象,参数...)来调用方法,
就等价于 对象.方法(参数)
但是方法的名称和参数都是运行时才确定的,它就是动态代理、测试框架、注解解释器等等功能的基础。
*/
| 对比 | 普通方式 | 反射方式 |
|---|---|---|
| 调用方法 | 对象.方法名(参数)(如 s.study()) |
method.invoke(对象, 参数...) |
| 方法的名称 | 编译期写死 | 运行时才确定(可以是字符串变量/配置) |
| 私有方法 | 编译期直接报错(private 不可见) | getDeclaredMethod + setAccessible(true) 可调用 |
| 静态方法 | 类名.方法名() |
invoke(null, 参数...) |
| 应用场景 | 普通业务 | 动态代理 / 测试框架(JUnit)/ 注解解释器 / 框架调用 setter |
要点:
- invoke 等价于
对象.方法(参数)——反射调用方法就是"运行时动态调用"- 方法与 Field/Constructor 套路一致:
getMethod(s)(public 含继承)vsgetDeclaredMethod(s)(本类全部含 private)+setAccessible(true)暴力反射- 典型应用:框架反射调用实体类的 setter 给属性赋值、getter 取属性值;测试框架反射调用被测试方法;动态代理底层基于 Method.invoke
// 来源:course/Demo02.java(类注释)
/*
Class对象获取Method对象的方法:
public Method[] getMethods() 返回所有的public方法,包括父类/接口继承的,比如toString
public Method getMethod(String name, Class<?>... parameterTypes) 返回指定的单个public方法
public Method[] getDeclaredMethods() 返回所有的方法,含有private,但不包括父类/接口的方法
public Method getDeclaredMethod(String name, Class<?>... parameterTypes) 返回指定的单个方法含有private
*/
| 方法 | 作用 | 是否含 private | 是否含父类/接口继承 |
|---|---|---|---|
getMethods() |
返回所有 public 方法数组 | ❌ 只 public | ✅ 含父类/接口继承的(如 toString/hashCode) |
getMethod(name, parameterTypes) |
返回指定的单个 public 方法 | ❌ 只 public | ✅ 含父类/接口继承的 |
getDeclaredMethods() |
返回本类所有方法数组 | ✅ 含 private | ❌ 不含父类/接口的方法 |
getDeclaredMethod(name, parameterTypes) |
返回指定的单个方法 | ✅ 含 private | ❌ 不含父类/接口的方法 |
要点:
- 口诀与 Field/Constructor 完全一致:含
Declared的能拿到私有(本类全部、不含父类/接口);不含Declared的只能拿 public(含父类/接口继承)getMethods()会包含继承的方法:比如 Student 没写toString,但getMethods()能拿到 Object 的toString/hashCode/equals/getClass等——这是 Method 与 Field 的"含继承"表现(Field 的getFields()也含父类 public 字段)
// 来源:course/Demo02.java(类注释)
/*
如果遇到重载的方法(方法名相同,参数列表不同)那么就必须要指明参数的类型比如:
getMethod("setName", String.class) 中的String.class就是用来区分同名方法重载的,
如果参数类型不写,或写错,就会抛异常。
*/
| 写法 | 说明 |
|---|---|
getMethod("setName", String.class) |
指定参数类型,找到 setName(String) 这个重载版本 |
getMethod("setName") |
不写参数类型——如果存在多个同名重载方法,无法区分会抛异常(NoSuchMethodException) |
getMethod("getAge", Integer.class) |
参数类型写错也会抛异常——必须与实际方法签名完全匹配 |
要点:
- 同名重载方法(方法名相同、参数列表不同)必须传参数类型来区分——
String.class就是用来区分哪个重载版本的- 参数类型不写或写错都会抛异常(
NoSuchMethodException)——所以getMethod("setName", String.class)中的String.class必不可少
// 来源:course/Demo02.java(类注释)
/*
Method对象的方法:
public Object invoke(Object obj, Object... args) 调用方法,返回方法的执行结果
obj 是某个对象,指的是执行某个对象的指定方法
args 参数列表,调用方法时需要传入的实际参数
public void setAccessible(boolean flag) 暴力反射
public Class<?> getReturnType() 获取方法的返回值类型
public Class<?>[] getParameterTypes() 获取方法的参数类型数组
public int getModifiers() 获取方法的修饰符
*/
| 方法 | 作用 | 说明 |
|---|---|---|
invoke(Object obj, Object... args) |
调用方法,返回方法的执行结果 | obj 是执行该方法的对象,args 是实际参数列表;返回 Object(可强转) |
setAccessible(boolean flag) |
暴力反射:临时取消访问权限检查 | 调用 private 方法前必须 setAccessible(true) |
getReturnType() |
获取方法的返回值类型 | 返回 Class<?>(如 String.class / void.class) |
getParameterTypes() |
获取方法的参数类型数组 | 返回 Class<?>[],逐个可取参数类型 |
getModifiers() |
获取方法的修饰符 | 返回 int,用 Modifier.toString() 解析 |
// 来源:course/Demo02.java(类注释)
/*
注意:
如果是实例方法(通过对象调用的) invoke(对象,参数...) 第一个参数是调用该方法的对象
如果是类方法(使用static修饰的类方法) invoke(null,参数...) 第一个参数是null
如果没有返回值(void):调用invoke()会返回null
如果是private方法,那么必须要先暴力反射setAccessible(true)
*/
| 方法类型 | invoke 写法 | 说明 |
|---|---|---|
| public 实例方法 | invoke(对象, 参数...) |
第一个参数是调用该方法的对象(如 invoke(s) 等价于 s.getName()) |
| static 静态方法 | invoke(null, 参数...) |
第一个参数是 null(类方法不属于对象,与 Field 操作静态属性传 null 同一规律) |
| void 无返回值方法 | invoke(对象, 参数...) |
调用 invoke() 会返回 null(void 方法没有返回值) |
| private 私有方法 | 先 setAccessible(true) 再 invoke(...) |
必须暴力反射放开权限,否则抛 IllegalAccessException |
要点:
- invoke 第一参数 = 调用方法的对象:实例方法传对象、静态方法传 null(回顾 Field 操作静态属性传 null 的同一规律)
- invoke 返回 Object:方法有返回值时
invoke返回该返回值(可强转成目标类型);void 方法返回 null- private 方法必须暴力反射:
getDeclaredMethod拿到 +setAccessible(true)+invoke——与 Field/Constructor 的暴力反射套路完全一致- 反射三大成员统一套路:
getDeclaredXxx拿私有 +setAccessible(true)放开权限 + 执行操作(Constructor→newInstance / Field→get/set / Method→invoke)
Demo02 演示了方法反射的全部四个应用:遍历所有方法(含参数和返回值信息)、调用 public 实例方法(无参/带参)、暴力反射调用 private 实例方法、调用 static 静态方法。
// 来源:course/Demo02.java
public class Demo02 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class<Student> studentClass = Student.class;
//1、获取所有的public方法(含父类/接口继承的,如toString)
Method[] methods = studentClass.getMethods();
for (Method method : methods){
Class<?>[] parameterTypes = method.getParameterTypes();
List<String> parameters = Arrays.stream(parameterTypes)
.map(p -> p.getSimpleName()).collect(Collectors.toList());
System.out.println(method.getName()+"("+parameters+") 返回值="+method.getReturnType().getSimpleName());
}
System.out.println("========本类的所有方法==========");
// getDeclaredMethods() 遍历本类全部方法(含 private)
for (Method method: studentClass.getDeclaredMethods()){
Class<?>[] parameterTypes = method.getParameterTypes();
List<String> parameters = Arrays.stream(parameterTypes)
.map(p -> p.getSimpleName()).collect(Collectors.toList());
System.out.println(method.getName()+"("+parameters+") 返回值="+method.getReturnType().getSimpleName());
}
//2、执行调用某个对象的实例方法
Student s=new Student(1001,"张三",25,"男","zhangsan@163.com","13636363366");
// 调用 public 无参实例方法 getName()——invoke 返回 Object
Method getNameMethod = studentClass.getMethod("getName");
Object name = getNameMethod.invoke(s);
System.out.println(name);
// 调用 public 带参实例方法 setName(String)——重载必须指明参数类型
Method setNameMethod = studentClass.getMethod("setName", String.class);
setNameMethod.invoke(s, "李四"); //赋值操作不需要返回值
System.out.println(s.getName());
//暴力反射,执行私有方法 read()——private 实例方法
Method readMethod = studentClass.getDeclaredMethod("read");
readMethod.setAccessible(true);
readMethod.invoke(s); //执行方法
//执行静态方法 write()——static 方法 invoke 传 null
Method writeMethod = studentClass.getDeclaredMethod("write");
writeMethod.setAccessible(true);
writeMethod.invoke(null);
}
}
...(getMethods() 输出所有 public 方法:getId/setId/getName/setName/.../study + Object 继承的 toString/hashCode/equals/getClass 等)...
========本类的所有方法==========
setName([String]) 返回值=void
setSchool([String]) 返回值=void
write() 返回值=void
getSchool() 返回值=String
...
张三 ← getMethod("getName").invoke(s) 返回 name 值
李四 ← getMethod("setName", String.class).invoke(s, "李四") 后 s.getName()
李四读....... ← 暴力反射调用 private read():name 已是"李四"
静态方法...写............... ← 暴力反射调用 private static write():invoke(null)
| 代码 | 作用 | 说明 |
|---|---|---|
studentClass.getMethods() |
获取所有 public 方法(含父类/接口继承) | 遍历打印方法名、参数类型、返回值类型 |
method.getParameterTypes() |
获取参数类型数组 | Arrays.stream(...) 用 Stream 流把 Class<?> 映射成简单名 List(回顾 Stream 流知识) |
method.getReturnType().getSimpleName() |
获取返回值类型简单名 | 如 String / void |
studentClass.getDeclaredMethods() |
获取本类全部方法(含 private,不含父类/接口) | 输出比 getMethods 多出 read/write 两个私有方法 |
studentClass.getMethod("getName") |
获取 public 无参方法 | invoke(s) 等价于 s.getName(),返回 Object 打印 张三 |
studentClass.getMethod("setName", String.class) |
获取 public 带参方法(指明参数类型区分重载) | invoke(s, "李四") 等价于 s.setName("李四"),赋值操作不需要返回值 |
getDeclaredMethod("read") + setAccessible(true) + invoke(s) |
暴力反射调用 private 实例方法 | 必须放开权限;name 已改为"李四",输出 李四读....... |
getDeclaredMethod("write") + setAccessible(true) + invoke(null) |
暴力反射调用 private 静态方法 | 静态方法 invoke 第一参数传 null,输出 静态方法...写............... |
要点:
- getMethods() 含继承方法:Student 没写
toString,但 getMethods() 能拿到 Object 的toString/hashCode等——Demo02 用 Stream 流把参数类型Class<?>[]转成List<String>简单名再打印- getMethod 调用实例方法:
invoke(s)第一个参数是执行方法的对象;带参方法invoke(s, "李四")传实际参数- void 方法返回值:
setName是 void,invoke返回 null,不需要接收返回值- 私有方法必须暴力反射:
read()/write()是 private,用getDeclaredMethod+setAccessible(true);静态方法 invoke 传 null- 与 Field 反射呼应:操作静态成员(属性/方法)对象参数都传 null——本课 Field 操作静态属性 + Method 调用静态方法,规律统一
学完了反射的三大核心成员(Constructor / Field / Method)+ 获取 Class 对象后,Demo03 用一段类注释把整个反射知识体系整理成 4 类全景地图——它是"反射到底有哪些 API"的总索引:先分清"针对哪一类做反射"(Class/Field/Method/Constructor),再记住"每一类反射关注什么、有哪些方法"。Demo03 没有 main 方法,它就是一张纯知识点的知识地图。
// 来源:course/Demo03.java(类注释)
/*
如果想要弄明白反射,需要掌握以下内容:
1、针对类的反射--Class-就需要记住类里面有哪些成员?
|- 成员属性
|- 获取一个对象:getField()
|- 获取多个对象(数组):getFields()
|- 获取一个私有对象:getDeclaredField()
|- 获取多个私有对象:getDeclaredFields()
|- 成员方法
|- 获取对象:getMethod()
|- 获取多个对象(数组):getMethods()
|- 获取一个私有对象:getDeclaredMethod()
|- 获取多个私有对象:getDeclaredMethods()
|- 构造方法
|- 获得对象:getConstructor()
|- 获取多个对象(数组):getConstructors()
|- 获取一个私有对象:getDeclaredConstructor()
|- 获取多个私有对象:getDeclaredConstructors()
2、针对属性的反射--Field-就需要记住属性的组成部分有哪些?
|- 属性变量名 getName()
|- 属性修饰符 getModifiers()
|- 属性值
|- 获取值 get()
|- 赋值 set()
3、针对方法的反射--Method-就需要记住方法的组成部分有哪些?
|- 方法名称 getName()
|- 执行方法 invoke()
|- 方法返回值类型 getReturnType()
|- 方法的参数列表类型 getParameterTypes()
|- 方法的修饰符 getModifiers()
4、针对构造放过的反射--Constructor--就需要记住构造方法的组成部分有哪些?
|- 构造方法的修饰符 getModifiers()
|- 构造方法的参数列表类型 getParameterTypes()
|- 创建对象 newInstance()
*/
| 反射类别 | 关注点 | 获取对象的 API(4 个/类) | 对象自身的常用方法 |
|---|---|---|---|
| ① Class 类反射 | 类里面有哪些成员(属性/方法/构造) | 属性:getField/getFields/getDeclaredField/getDeclaredFields;方法:getMethod/getMethods/getDeclaredMethod/getDeclaredMethods;构造:getConstructor/getConstructors/getDeclaredConstructor/getDeclaredConstructors |
— |
| ② Field 属性反射 | 属性的组成部分(名字/修饰符/值) | 获取方式见 Class 反射 | 属性名 getName()、修饰符 getModifiers()、值 get() / set() |
| ③ Method 方法反射 | 方法的组成部分(名字/执行/返回值/参数/修饰符) | 获取方式见 Class 反射 | 方法名 getName()、执行 invoke()、返回值类型 getReturnType()、参数类型 getParameterTypes()、修饰符 getModifiers() |
| ④ Constructor 构造反射 | 构造方法的组成部分(修饰符/参数/创建对象) | 获取方式见 Class 反射 | 修饰符 getModifiers()、参数类型 getParameterTypes()、创建对象 newInstance() |
① 获取 API 的规律(3 组 × 4 个 = 12 个):每个成员(属性/方法/构造)都有同样的 4 个获取方法:
| 成员 | 取多个(数组) | 取单个 | 取多个含私有 | 取单个含私有 |
|---|---|---|---|---|
| 属性 | getFields() |
getField(name) |
getDeclaredFields() |
getDeclaredField(name) |
| 方法 | getMethods() |
getMethod(name, 参数类型...) |
getDeclaredMethods() |
getDeclaredMethod(name, 参数类型...) |
| 构造 | getConstructors() |
getConstructor(参数类型...) |
getDeclaredConstructors() |
getDeclaredConstructor(参数类型...) |
记忆口诀:含
Declared的能拿私有(本类全部),不含Declared的只能拿 public(含父类/接口继承)——三组获取 API 共用同一条口诀
② 每类反射对象的关键方法:
getName()、修饰符 getModifiers()、值 get() / set()getName()、执行 invoke()、返回值类型 getReturnType()、参数类型 getParameterTypes()、修饰符 getModifiers()getModifiers()、参数类型 getParameterTypes()、创建对象 newInstance()getModifiers()(返回 int,Modifier.toString() 解析)和 getParameterTypes()(Constructor/Method 有参数,Field 无参数)要点:
- 反射的总纲:先"拿 Class"→ 再"按成员类型拿反射对象"(Field/Method/Constructor)→ 最后"用反射对象操作成员"(get/set / invoke / newInstance)——Demo03 把这条路整理成了知识地图
- 12 个获取 API 有统一规律:每个成员都是"4 个获取方法",只是前缀从 Field/Method/Constructor 换一下——记住一个就记住全部
- 本章"作业答案参考":练习五答案
exericse/MethodPrintUtilTest.java(遍历方法时用了setAccessible(true)暴力反射 +Modifier.toString+ 字符串修复去掉方括号)、练习六答案exericse/MethodInvokeTest.java(invokeMethod通用工具用args[i].getClass()动态计算参数类型数组)
| 知识点 | 核心内容 | 来源 |
|---|---|---|
| Field 概念 | Field 对象代表类中的一个成员变量(属性/字段);反射通过 get() 读值、set() 赋值 |
course/Demo01 注释 |
| Field 的框架应用 | 框架查询数据库后,把数据库数据一个一个映射到 Java 类中用的就是 Field.set() 赋值(ORM 底层原理) | course/Demo01 注释 |
| getFields() | 返回所有 public 字段数组,包括从父类继承的 | course/Demo01 注释 |
| getField(String name) | 返回指定的单个 public 字段,包括从父类继承的 | course/Demo01 注释 |
| getDeclaredFields() | 返回本类(不含父类)声明的所有字段数组,包括 private | course/Demo01 注释 |
| getDeclaredField(String name) | 返回指定的单个字段,包括 private | course/Demo01 注释 |
| Declared 口诀 | 含 Declared 的能拿到私有(本类全部),不含 Declared 的只能拿 public(含父类继承)——与 Constructor 一致 | course/Demo01 注释 |
| Field.get(Object obj) | 读取对象 obj 的该属性值,返回 Object(可能需要强转) | course/Demo01 注释 |
| Field.set(Object obj, Object value) | 设置对象 obj 的该属性值(参数:对象 + 值) | course/Demo01 注释 |
| Field.setAccessible(true) | 暴力反射:临时取消访问权限检查,访问 private 字段必须调用 | course/Demo01 注释 |
| Field.getType() | 获取属性的类型,返回 Class<?>(再调 getSimpleName() 得类型名) |
course/Demo01 注释 |
| Field.getName() | 获取属性名 | course/Demo01 注释 |
| Field.getModifiers() | 获取属性的修饰符(返回 int),用 Modifier.toString() 解析 |
course/Demo01 注释 |
| Modifier.toString(int) | 把修饰符 int 值解析成可读字符串(private / public / public static...) | course/Demo01 main |
| 演示①:遍历所有属性 | getDeclaredFields() + field.getModifiers() + Modifier.toString() + field.getType().getSimpleName() + field.getName() 输出每个字段的修饰符/类型/属性名 |
course/Demo01 main |
| 演示②:操作 public 字段 | clazz.getField("email") + get(s) 读取 + set(s, 新值) 修改;反射 set 后 s.email 直接访问同样生效 |
course/Demo01 main |
| 演示③:操作 private 字段 | clazz.getDeclaredField("name") + setAccessible(true) 暴力反射 + get(s)/set(s, 新值) |
course/Demo01 main |
| 演示④:操作 static 字段 | 静态成员属于整个类、不属于任何对象,get(null) / set(null, 值)——对象参数传 null |
course/Demo01 main |
| 静态属性传 null 的原理 | 静态成员(静态属性/静态方法/静态代码块)不属于某个对象,而是属于整个类的,所以不传对象参数、传 null | course/Demo01 main |
| 反射赋值与普通访问同一份数据 | field.set(s, ...) 修改后 s.email / Student.school 直接访问也是新值 |
course/Demo01 main |
| Student 字段设计 | private(id/name/age/gender)+ public(email/phone)+ public static(school)——三种权限对应三段演示 | course/Student.java |
| Student 构造方法 | public 无参 / private 二参 / public 四参 / public 六参——本课用六参构造创建对象,上一课演示过 Constructor 反射 | course/Student.java |
| Student 新增 getter/setter | 7 组 getter/setter(id/name/age/gender/email/phone 实例 + school 静态)——Demo02 用 getMethod("getName")/getMethod("setName", String.class) 反射调用 |
course/Student.java |
| Student 私有方法 read/write | private void read()(实例)+ private static void write()(静态)——Demo02 暴力反射调用目标(getDeclaredMethod + setAccessible(true) + invoke) |
course/Student.java |
| student.study() | Student 的实例方法 输出 name+"在学习....." |
course/Student.java |
| Method 概念 | Method 对象代表类中的一个方法;反射通过 Method.invoke(对象, 参数...) 调用方法,等价于 对象.方法(参数),方法名称和参数运行时才确定——动态代理/测试框架/注解解释器的基础 |
course/Demo02 注释 |
| getMethods() | 返回所有 public 方法数组,包括父类/接口继承的(如 toString/hashCode/getClass) | course/Demo02 注释 |
| getMethod(name, parameterTypes) | 返回指定的单个 public 方法;重载方法必须传参数类型区分(如 getMethod("setName", String.class)),不写/写错抛异常 |
course/Demo02 注释 |
| getDeclaredMethods() | 返回本类所有方法数组,包括 private,但不包括父类/接口的方法 | course/Demo02 注释 |
| getDeclaredMethod(name, parameterTypes) | 返回指定的单个方法,包括 private | course/Demo02 注释 |
| Method.invoke(obj, args) | 调用方法,返回方法的执行结果(Object 可强转);obj 是执行该方法的对象,args 是实际参数 |
course/Demo02 注释 |
| Method.setAccessible(true) | 暴力反射:临时取消访问权限检查,调用 private 方法必须调用 | course/Demo02 注释 |
| Method.getReturnType() | 获取方法的返回值类型(如 String.class / void.class) | course/Demo02 注释 |
| Method.getParameterTypes() | 获取方法的参数类型数组(Class<?>[]) |
course/Demo02 注释 |
| Method.getModifiers() | 获取方法的修饰符(返回 int,Modifier.toString() 解析) |
course/Demo02 注释 |
| invoke 四种调用规律 | 实例方法 invoke(对象,参数...) 第一参数传对象;静态方法 invoke(null,参数...) 传 null;void 方法 invoke 返回 null;private 方法必须先 setAccessible(true) |
course/Demo02 注释 |
| 演示:遍历方法 | getMethods()/getDeclaredMethods() 遍历 + getParameterTypes()(Stream 映射成简单名 List)+ getReturnType().getSimpleName() 输出每个方法的方法名/参数/返回值 |
course/Demo02 main |
| 演示:调用实例方法 | getMethod("getName") + invoke(s) 调用无参方法返回 name;getMethod("setName", String.class) + invoke(s, "李四") 调用带参方法(void 不接收返回值) |
course/Demo02 main |
| 演示:暴力反射私有方法 | getDeclaredMethod("read") + setAccessible(true) + invoke(s) 调用 private 实例方法;getDeclaredMethod("write") + setAccessible(true) + invoke(null) 调用 private 静态方法(传 null) |
course/Demo02 main |
| 反射三大成员统一套路 | Constructor(newInstance 创建对象)/ Field(get/set 读写字段)/ Method(invoke 调用方法)——Declared 拿私有 + setAccessible(true) 放开权限,操作静态成员(属性/方法)传 null | course/Demo01 + Demo02 |
| 受检异常 | Demo01 main throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException(Field);Demo02 main throws NoSuchMethodException, InvocationTargetException, IllegalAccessException(Method)——方法名/参数类型不匹配抛 NoSuchMethodException、被调方法内部抛异常时 invoke 抛 InvocationTargetException |
course/Demo01 + Demo02 main |
| 反射知识全景总览(Demo03) | 把整个反射体系整理成 4 类知识地图:①Class 类反射(类里的成员——属性/方法/构造)②Field 属性反射(属性的组成部分)③Method 方法反射(方法的组成部分)④Constructor 构造反射(构造方法的组成部分)——Demo03 没有 main 方法,是纯知识点类 | course/Demo03 注释 |
| 12 个获取 API 的统一规律 | 属性/方法/构造各有 4 个获取方法(数组/单个/含私有数组/含私有单个)——getXxx(s) / getDeclaredXxx(s),口诀"含 Declared 的能拿私有"统一适用于三组 |
course/Demo03 注释 |
| Field 属性三部分组成 | 属性变量名 getName() / 属性修饰符 getModifiers() / 属性值 get() + set() |
course/Demo03 注释 |
| Method 方法五部分组成 | 方法名称 getName() / 执行方法 invoke() / 返回值类型 getReturnType() / 参数列表类型 getParameterTypes() / 修饰符 getModifiers() |
course/Demo03 注释 |
| Constructor 构造三部分组成 | 构造方法修饰符 getModifiers() / 参数列表类型 getParameterTypes() / 创建对象 newInstance() |
course/Demo03 注释 |
| 练习五答案参考 | exericse/MethodPrintUtilTest.java——printDeclaredMethods 遍历时用 setAccessible(true) 暴力反射 + Modifier.toString 解析修饰符 + 字符串修复(replace 去掉参数类型 List 的 [ ] 方括号) |
exericse/MethodPrintUtilTest.java |
| 练习六答案参考 | exericse/MethodInvokeTest.java——invokeMethod 通用工具用 args[i].getClass() 动态计算参数类型数组(不写死参数类型),再 getMethod(methodName, classes) + invoke(obj, args);无参数与有参数两种分支处理 |
exericse/MethodInvokeTest.java |
| exericse/Student 实体类 | 练习包 exericse 的实体类:private(id/name/age)+ public static(school)+ 新增 getter/setter + eat(String s) 方法(打印"吃:s")——练习五/六的反射目标 |
exericse/Student.java |
get() 读取字段值、set() 修改字段值;能说出框架中的应用(ORM 框架查询数据库后用 Field.set() 把数据映射到 Java 类)getFields()(所有 public 数组,含父类继承)/ getField(String name)(指定 public)/ getDeclaredFields()(本类全部含 private,不含父类)/ getDeclaredField(String name)(指定含 private)——背口诀:含 Declared 的能拿私有(本类全部),不含 Declared 的只能拿 public(含父类继承)get(对象) 读值 / set(对象, 值) 赋值 / setAccessible(true) 暴力反射 / getType() 类型 / getName() 属性名 / getModifiers() 修饰符(int 需 Modifier.toString 解析)course/Demo01,观察输出——先打印 Student 全部 7 个字段(private/public/public static 三种修饰符),再验证 public email 的读取/修改、private name 的暴力反射、static school 的传 null 操作emailField.getModifiers() 直接输出一个 int 数字(如 1),用 Modifier.toString() 解析后才得到 public——理解"修饰符底层是 int 常量值"nameField.setAccessible(true) 注释掉再运行,观察抛 IllegalAccessException——直观体会"不 setAccessible(true) 无法访问 private 字段"(与上一课访问 private 构造方法完全一致的套路)clazz.getDeclaredField("name") 改成 clazz.getField("name"),运行观察抛 NoSuchFieldException——体会"getField 只能拿 public 字段"的口诀schoolField.set(null, "福建桥梁职业技术学院") 之后 Student.school 直接访问输出新值——体会"反射 set 与普通访问同一份数据、静态字段全类共享"setAccessible(true);对比 Constructor(newInstance 创建对象)与 Field(get/set 读写字段)的差异getDeclaredXxx 拿私有 + setAccessible(true) 放开权限 + 执行操作printFields(Object obj):传入任意对象,用 getDeclaredFields() 遍历打印所有字段的修饰符/类型/属性名——体会"反射可以做通用工具"(回顾上一课 printUtil 思路)throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException——getField/getDeclaredField 字段名写错抛 NoSuchFieldException、private 未放开权限操作抛 IllegalAccessException、Class.forName 类名写错抛 ClassNotFoundException——回顾 07-28 异常处理知识Method.invoke(对象, 参数...) 调用方法,等价于 对象.方法(参数),方法的名称和参数运行时才确定;能说出应用场景(动态代理、测试框架、注解解释器、框架调用 setter/getter)getMethods()(所有 public 数组,含父类/接口继承如 toString)/ getMethod(name, parameterTypes)(指定 public)/ getDeclaredMethods()(本类全部含 private,不含父类/接口)/ getDeclaredMethod(name, parameterTypes)(指定含 private)——口诀与 Field/Constructor 完全一致:含 Declared 的能拿私有,不含 Declared 的只能拿 publicgetMethod("setName", String.class) 中的 String.class 是用来区分同名重载方法的——不写参数类型或写错都会抛 NoSuchMethodException;试着重载一个 setName(String, Integer) 并分别用两种签名获取invoke(obj, 参数...) 调用方法 / setAccessible(true) 暴力反射 / getReturnType() 返回值类型 / getParameterTypes() 参数类型数组 / getModifiers() 修饰符invoke(对象, 参数...) 第一参数传对象 ②静态方法 invoke(null, 参数...) 传 null ③void 方法 invoke 返回 null ④private 方法必须先 setAccessible(true)——与 Field 操作静态属性传 null 同一规律course/Demo02,观察输出——getMethods() 遍历打印所有 public 方法(含 Object 继承的 toString/hashCode/getClass 等)+ Stream 处理参数类型、getDeclaredMethods() 输出本类全部方法(含 read/write 私有方法)、getMethod("getName").invoke(s) 打印"张三"、getMethod("setName", String.class).invoke(s, "李四") 后打印"李四"、暴力反射 read() 输出"李四读......."、静态方法 write() 输出"静态方法...写..............."getMethod("getName")(实例)和 getMethod("getSchool")(静态)调用——体会实例方法 invoke 第一参数传对象 s、静态方法传 null;把静态方法的 invoke 第一参数误传成对象 s,观察它是否也能执行(static 方法本质不依赖对象,传对象也会被忽略)readMethod.setAccessible(true) 注释掉再运行,观察抛 IllegalAccessException——体会"private 方法不放开权限无法 invoke"studentClass.getMethod("read")(不带 Declared)尝试获取 private 方法,观察抛 NoSuchMethodException——验证"getMethod 只能拿 public"的口诀setNameMethod.invoke(s, "李四") 返回 null(void 方法),打印 System.out.println(setNameMethod.invoke(s, "李四")) 观察输出 null;对比 getNameMethod.invoke(s) 返回 "张三"——体会"无返回值方法 invoke 返回 null"read() 方法体内故意写一个会抛异常的语句(如 int x = 1/0),反射 invoke 调用它,观察抛 InvocationTargetException(被调方法内部异常被包装)——回顾 07-28 异常处理知识exericse/OrmFieldMapTest.java(含 exericse/Student.java 实体类)——运行观察静态字段传 null 操作 + Map 数据库行映射到 Student 对象的完整流程getFields/getField/getDeclaredFields/getDeclaredField(属性)、getMethods/getMethod/getDeclaredMethods/getDeclaredMethod(方法)、getConstructors/getConstructor/getDeclaredConstructors/getDeclaredConstructor(构造)——体会"三组 12 个 API 规律完全一致"getModifiers() 修饰符 + getName()),以及各自独有的方法(Field:get/set;Method:invoke/getReturnType/getParameterTypes;Constructor:newInstance/getParameterTypes)——画一张对比表exericse/MethodPrintUtilTest.java——printDeclaredMethods 遍历时对每个方法 setAccessible(true) + Modifier.toString 解析修饰符 + 字符串修复(replace 去掉参数类型 List 的 [ ]);运行观察输出 private void write() 这类带修饰符的方法行exericse/MethodInvokeTest.java——invokeMethod 通用工具用 args[i].getClass() 动态计算参数类型数组(不需要写死 String.class),并区分"有参数/无参数"两个分支;main 里调用 invokeMethod(s, "eat", "饭...") 调用 exericse/Student 新增的 eat(String) 方法getDeclaredXxx 拿私有 + setAccessible(true) 放开权限 + 执行操作;至此反射三件套(建对象/读写属性/调方法)可组合完成框架级操作InvocationHandler.invoke(proxy, method, args),其中 method.invoke(target, args) 反射调用真实对象的方法——这正是本课"Method 是动态代理的基础"论断的经典实现;后续学习 Spring AOP(基于动态代理)时会反复见到getDeclaredMethods() 遍历类的全部方法,找出带 @Test 注解的方法,再 method.invoke(测试对象) 执行——"注解 + 反射"是测试框架的底层原理,本课 Demo02 的遍历方法正是这个套路getMethod("set" + 属性名首字母大写, 类型.class) + invoke(obj, 值) 反射调用 setter 赋值——这就是为什么实体类要写标准 getter/setter(本课 Student 新增的 7 组 getter/setter 正是为此准备)getParameterTypes() 拿到方法的参数类型数组(Class<?>[]),这与 getMethod(name, parameterTypes...) 的传参一一对应——重载方法(同名不同参)在 Class 对象里其实是多个独立的 Method 对象,必须靠参数类型定位到具体那一个invoke(obj, 参数...) 传入的实际参数会与方法的参数类型匹配(基本类型自动装箱,如 setAge(Integer) 传 int 值 25 会被自动装箱成 Integer);参数类型不匹配或数量不对抛 IllegalArgumentExceptioninvoke 调用方法时,如果被调方法内部抛异常,invoke 会把异常包装成 InvocationTargetException 抛出(而不是直接抛原异常)——调用者需要 getCause() 拿到真正的异常;这是反射调用方法与普通调用的一个隐蔽差异Constructor.newInstance()(可传参、可访问 private 构造),废弃了 Class.newInstance()(只能无参且权限检查严格)——同理反射调用方法统一用 Method.invoke 而非老旧的 Class.getMethod 直接裸调getConstructor().newInstance() 创建对象(08-12)+ getDeclaredField().set() 给字段赋值(Field)+ getDeclaredMethod().invoke() 调用方法(Method)——就是 MyBatis 结果映射、Spring Bean 初始化、测试框架执行测试用例的完整底层链路Modifier.toString() 内部就是按位与运算拼接出字符串;Modifier.isPublic(int) / Modifier.isStatic(int) 等 isXxx 方法可单独判断某个修饰符是否存在setAccessible(true) 本质是绕过 Java 访问控制检查;在模块化系统(JPMS)中,跨模块访问私有成员还受 --add-opens 参数约束——Spring 等框架在 JDK17+ 运行时需要配置 JVM 参数放开模块限制getField/getDeclaredField 本身有开销,框架(如 MyBatis)通常在启动阶段一次性获取并缓存 Field 对象,运行时只调用 get()/set()——这是"框架用反射但性能不差"的关键优化手段setAccessible(true) 不仅放开权限,还会让 JVM 为字段访问做更快的内部优化(绕过访问检查的反射调用性能显著提升)——所以框架总是对私有字段先 setAccessible(true) 再操作,既是"能访问"也是"更高效"userName)与数据库列名(如 user_name)往往不一致——框架通过配置或约定(驼峰↔下划线转换)拿到字段名后,用反射 Field.set() 赋值,这正是"配置文件/注解驱动反射"的典型场景getDeclaredFields() + setAccessible(true) + field.get(对象),与本课 Demo01 的演示逻辑一致getModifiers()(构造修饰符)和 getParameterTypes()(构造参数类型)——上一课(08-12)主要用了 newInstance() 创建对象,这两个方法是构造反射的补充维度,可在练习中试用(打印构造方法的修饰符与参数类型)args[i].getClass() 动态计算参数类型"实现通用 invoke——这两种技巧(字符串清理、动态参数类型推导)是写反射通用工具的常用套路,后续做框架级工具时可直接复用java.lang.Class(getFields/getField/getDeclaredFields/getDeclaredField + getMethods/getMethod/getDeclaredMethods/getDeclaredMethod + getConstructors/getConstructor/getDeclaredConstructors/getDeclaredConstructor)、java.lang.reflect.Field(get/set/setAccessible/getType/getName/getModifiers)、java.lang.reflect.Method(invoke/setAccessible/getReturnType/getParameterTypes/getModifiers)、java.lang.reflect.Constructor(newInstance/setAccessible/getModifiers/getParameterTypes)、java.lang.reflect.Modifier(toString/isPublic/isStatic 等修饰符解析工具类)