# 20260813 课堂笔记 — 反射 Field 字段反射与 Method 方法反射 - **日期**:2026-08-13 - **项目**:`c260813` - **包路径**:`course` - **作者**:WanJL - **内容概览**:反射 Field(属性/字段/变量)(Demo01 演示:Class 类获取 Field 对象的 4 个 API → 获取所有属性并输出修饰符和类型 → 读取/修改 public 属性 → setAccessible(true) 暴力反射读取/修改 private 属性 → 操作静态属性传 null)+ 反射 Method(方法)(Demo02 演示:Class 类获取 Method 对象的 4 个 API → 遍历方法输出方法名/参数/返回值 → invoke 调用实例方法/带参方法/私有方法/静态方法)+ Student 实体类(private/public/public static 字段设计 + 4 个构造方法 + 7 组 getter/setter + 私有方法 read/write) --- ## 目录 1. [反射 Field 概述:成员变量的反射操作](#1-反射-field-概述成员变量的反射操作) 2. [获取 Field 对象的 4 个 API:Declared 决定能否拿到私有字段](#2-获取-field-对象的-4-个-apideclared-决定能否拿到私有字段) 3. [Field 类的常用方法:get/set/setAccessible/getType/getName/getModifiers](#3-field-类的常用方法getsetsetaccessiblegettypegetnamegetmodifiers) 4. [Demo01 演示①:获取所有属性并输出修饰符和类型](#4-demo01-演示①获取所有属性并输出修饰符和类型) 5. [Demo01 演示②:读取/修改 public 属性](#5-demo01-演示②读取修改-public-属性) 6. [Demo01 演示③:读取/修改 private 属性——暴力反射 setAccessible(true)](#6-demo01-演示③读取修改-private-属性暴力反射-setaccessibletrue) 7. [Demo01 演示④:操作静态属性——对象参数传 null](#7-demo01-演示④操作静态属性对象参数传-null) 8. [Student 实体类解析:三种字段权限的反射目标](#8-student-实体类解析三种字段权限的反射目标) 9. [反射 Method 方法概述:方法的反射操作(Demo02)](#9-反射-method-方法概述方法的反射操作demo02) 10. [获取 Method 对象的 4 个 API:Declared 与重载参数](#10-获取-method-对象的-4-个-apideclared-与重载参数) 11. [Method 类的常用方法:invoke/setAccessible/getReturnType/getParameterTypes/getModifiers](#11-method-类的常用方法invokesetaccessiblegetreturntypegetparametertypesgetmodifiers) 12. [Demo02 演示:遍历方法 + invoke 调用实例/带参/私有/静态方法](#12-demo02-演示遍历方法-invoke-调用实例带参私有静态方法) 13. [知识点全景总结](#13-知识点全景总结) 14. [随堂练习要点](#14-随堂练习要点) 15. [拓展阅读](#15-拓展阅读) --- ## 1. 反射 Field 概述:成员变量的反射操作 ### 概念 —— Field 对象代表类中的一个成员变量 **Field 对象代表类中的一个成员变量(属性/字段)**。反射可以通过 Field 的 `get()` 读取字段值、`set()` 修改字段值。字段的反射操作在**框架中应用极广**: ```java // 来源:course/Demo01.java(类注释) /* Field对象代表类中的一个成员变量。反射可以通过Field的get()读取字段值、set()修改字段值。 这个在很多框架当中使用的都非常的多,比如我们使用框架查询数据库,把数据库的数据一个一个映射到我们自己创建的Java类中, 使用的就是File.set()来进行字段赋值。 */ ``` > **要点**: > - **Field = 成员变量/属性/字段**——反射中表示类的属性信息 > - **get() 读值、set() 赋值**——反射操作属性的两个核心方法 > - **框架中的典型应用**:ORM 框架查询数据库,把数据库查到的数据**一个一个映射到 Java 类中,用的就是 `Field.set()` 来进行字段赋值**——这是数据库记录 → Java 对象映射(ORM)的底层原理 ### 反射操作字段 vs 普通操作字段 | 对比 | 普通方式 | 反射方式 | |------|----------|----------| | 读取属性 | `对象.属性名`(如 `s.email`) | `field.get(对象)` | | 修改属性 | `对象.属性名 = 值` | `field.set(对象, 值)` | | 私有属性 | 编译期直接报错(private 不可见) | `getDeclaredField()` + `setAccessible(true)` 可访问 | | 静态属性 | `类名.属性名`(如 `Student.school`) | `field.get(null)` / `field.set(null, 值)` | > **要点**: > - 普通方式操作属性,**私有成员在编译期就无法访问**;反射方式可以动态拿到任意属性的 Field 对象(包括 private) > - 反射操作属性是"**运行时动态**"的——属性名可以是字符串变量/配置文件内容,这正是框架映射数据库字段时"字段名写死在配置里、运行时通过反射赋值"的原理 --- ## 2. 获取 Field 对象的 4 个 API:Declared 决定能否拿到私有字段 ### 概念 —— Class 类获取 Field 对象的四个方法 拿到 Class 对象后,用以下 4 个方法获取 Field 对象(**与上一课 Constructor 的 API 套路完全一致**): ```java // 来源: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) */ ``` ### 4 个 API 对比表 | 方法 | 作用 | 是否含 private | 是否含父类继承的字段 | |------|------|----------------|---------------------| | `getFields()` | 返回**所有 public 修饰**的字段属性数组 | ❌ 只 public | ✅ 包括从父类继承的 | | `getField(String name)` | 返回**指定的单个 public** 字段属性 | ❌ 只 public | ✅ 包括从父类继承的 | | `getDeclaredFields()` | 返回**本类声明的所有**字段属性数组 | ✅ 含 private | ❌ 不包含父类 | | `getDeclaredField(String name)` | 返回**指定的单个**字段属性 | ✅ 含 private | ❌ 不包含父类 | > **要点**: > - **记忆口诀(与 Constructor 一致)**:**含 `Declared` 的能拿到私有(包含所有),不含 `Declared` 的只能拿 public** > - **`Declared`(本类声明)的语义**:只拿**本类**里声明的字段,**不包含父类**;非 `Declared` 反而**包含从父类继承的 public 字段**——这正是 `getFields()` 与 `getDeclaredFields()` 的核心区别 > - **参数写法**:`getField(String name)` / `getDeclaredField(String name)` 传入**字段名字符串**(如 `"email"`),字段名不存在会抛 `NoSuchFieldException`(受检异常) --- ## 3. Field 类的常用方法:get/set/setAccessible/getType/getName/getModifiers ### 概念 —— 拿到 Field 对象后可以做什么 ```java // 来源: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`) --- ## 4. Demo01 演示①:获取所有属性并输出修饰符和类型 ### 概念 —— getDeclaredFields() 遍历类的全部字段 反射的第一个典型应用:**拿到一个类的所有属性,并逐一输出"修饰符 + 类型 + 属性名"**——这在框架底层(打印对象信息、生成 SQL、DTO 校验)非常常用。 ```java // 来源:course/Demo01.java public class Demo01 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { //获取Class对象 Class 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` --- ## 5. Demo01 演示②:读取/修改 public 属性 ### 概念 —— getField + get/set 操作公有字段 对于 **public 字段**,可以用 `getField("字段名")` 获取 Field 对象,再用 `get()` 读取、`set()` 修改: ```java // 来源:course/Demo01.java public class Demo01 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { //获取Class对象 Class 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)`) --- ## 6. Demo01 演示③:读取/修改 private 属性——暴力反射 setAccessible(true) ### 概念 —— getDeclaredField + setAccessible(true) 突破私有权限 private 字段在普通代码中**编译期就无法访问**,但反射可以拿到它的 Field 对象。**直接 `get()`/`set()` 会因权限检查报 `IllegalAccessException`**,必须先调用 `setAccessible(true)` **临时取消访问权限检查**——这就是**暴力反射**。 ```java // 来源:course/Demo01.java public class Demo01 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { //获取Class对象 Class 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)的暴力反射操作模式相同 --- ## 7. Demo01 演示④:操作静态属性——对象参数传 null ### 概念 —— 静态成员属于整个类,操作时不传对象传 null **静态成员(静态属性、静态方法、静态代码块)不属于某个对象,而是属于整个类的**。所以反射操作静态属性时,`get()`/`set()` 的**对象参数不传对象,传 null**。 ```java // 来源:course/Demo01.java public class Demo01 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { //获取Class对象 Class 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)` --- ## 8. Student 实体类解析:三种字段权限的反射目标 ### 概念 —— 一个覆盖了 public/private/static 的反射目标类 `Student` 是本课的反射目标实体类,字段设计刻意覆盖了**三种权限类型**,正好对应 Demo01 的三段演示: ```java // 来源: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, 值)` 操作(演示④) | ### 4 个构造方法(回顾上一课反射 Constructor) | 构造方法 | 权限 | 参数 | |----------|------|------| | `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 创建对象用) | ### 代码更新:新增 7 组 getter/setter 与 2 个私有方法(Demo02 的反射目标) Student 类同步更新,新增了**每个字段对应的 getter/setter**(id/name/age/gender/email/phone/school 共 7 组,school 的 getter/setter 是 **static** 的),以及 **2 个私有方法** `read()` / `write()`——它们正是 Demo02 方法反射(Method)的调用目标: ```java // 来源: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)**这一维度 --- ## 9. 反射 Method 方法概述:方法的反射操作(Demo02) ### 概念 —— Method 对象代表类中的一个方法 **Method 对象代表类中的一个方法**。反射通过 `Method.invoke(对象, 参数...)` 来调用方法,**等价于 `对象.方法(参数)`**——但方法的名称和参数都是**运行时才确定**的。这是反射三大核心成员(Constructor / Field / Method)的最后一位,也是**动态代理、测试框架、注解解释器**等高级功能的基础。 ```java // 来源:course/Demo02.java(类注释) /* Method对象代表类中的一个方法,反射通过Method.invoke(对象,参数...)来调用方法, 就等价于 对象.方法(参数) 但是方法的名称和参数都是运行时才确定的,它就是动态代理、测试框架、注解解释器等等功能的基础。 */ ``` ### 反射调用方法 vs 普通调用方法 | 对比 | 普通方式 | 反射方式 | |------|----------|----------| | 调用方法 | `对象.方法名(参数)`(如 `s.study()`) | `method.invoke(对象, 参数...)` | | 方法的名称 | 编译期写死 | **运行时才确定**(可以是字符串变量/配置) | | 私有方法 | 编译期直接报错(private 不可见) | `getDeclaredMethod` + `setAccessible(true)` 可调用 | | 静态方法 | `类名.方法名()` | `invoke(null, 参数...)` | | 应用场景 | 普通业务 | 动态代理 / 测试框架(JUnit)/ 注解解释器 / 框架调用 setter | > **要点**: > - **invoke 等价于 `对象.方法(参数)`**——反射调用方法就是"运行时动态调用" > - **方法与 Field/Constructor 套路一致**:`getMethod(s)`(public 含继承)vs `getDeclaredMethod(s)`(本类全部含 private)+ `setAccessible(true)` 暴力反射 > - **典型应用**:框架反射调用实体类的 setter 给属性赋值、getter 取属性值;测试框架反射调用被测试方法;动态代理底层基于 Method.invoke --- ## 10. 获取 Method 对象的 4 个 API:Declared 与重载参数 ### 概念 —— Class 类获取 Method 对象的四个方法 ```java // 来源: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 */ ``` ### 4 个 API 对比表 | 方法 | 作用 | 是否含 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 字段) ### 重载方法必须指明参数类型 ```java // 来源: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` 必不可少 --- ## 11. Method 类的常用方法:invoke/setAccessible/getReturnType/getParameterTypes/getModifiers ### 概念 —— 拿到 Method 对象后可以做什么 ```java // 来源: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()` 解析 | ### invoke 的四种调用规律(重点) ```java // 来源: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) --- ## 12. Demo02 演示:遍历方法 + invoke 调用实例/带参/私有/静态方法 ### 概念 —— 完整走一遍"拿 Class → 遍历方法 → 调用方法" Demo02 演示了方法反射的**全部四个应用**:遍历所有方法(含参数和返回值信息)、调用 public 实例方法(无参/带参)、暴力反射调用 private 实例方法、调用 static 静态方法。 ```java // 来源:course/Demo02.java public class Demo02 { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class studentClass = Student.class; //1、获取所有的public方法(含父类/接口继承的,如toString) Method[] methods = studentClass.getMethods(); for (Method method : methods){ Class[] parameterTypes = method.getParameterTypes(); List 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 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` 简单名再打印 > - **getMethod 调用实例方法**:`invoke(s)` 第一个参数是执行方法的对象;带参方法 `invoke(s, "李四")` 传实际参数 > - **void 方法返回值**:`setName` 是 void,`invoke` 返回 null,不需要接收返回值 > - **私有方法必须暴力反射**:`read()`/`write()` 是 private,用 `getDeclaredMethod` + `setAccessible(true)`;**静态方法 invoke 传 null** > - **与 Field 反射呼应**:操作静态成员(属性/方法)对象参数都传 null——本课 Field 操作静态属性 + Method 调用静态方法,规律统一 --- ## 13. 知识点全景总结 | 知识点 | 核心内容 | 来源 | |--------|----------|------| | **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 | --- ## 14. 随堂练习要点 - **Field 概念复述(必做)**:用一句话说清"什么是 Field 反射"——Field 对象代表类中的一个成员变量,反射通过 `get()` 读取字段值、`set()` 修改字段值;能说出框架中的应用(ORM 框架查询数据库后用 Field.set() 把数据映射到 Java 类) - **获取 Field 的 4 个 API 背诵(必做)**:`getFields()`(所有 public 数组,含父类继承)/ `getField(String name)`(指定 public)/ `getDeclaredFields()`(本类全部含 private,不含父类)/ `getDeclaredField(String name)`(指定含 private)——背口诀:**含 Declared 的能拿私有(本类全部),不含 Declared 的只能拿 public(含父类继承)** - **Field 常用方法背诵(必做)**:`get(对象)` 读值 / `set(对象, 值)` 赋值 / `setAccessible(true)` 暴力反射 / `getType()` 类型 / `getName()` 属性名 / `getModifiers()` 修饰符(int 需 Modifier.toString 解析) - **getFields vs getDeclaredFields 对比**:思考两者的区别——getFields 拿 public + 父类继承;getDeclaredFields 拿本类全部(含 private)不含父类。给 Student 加一个父类字段,分别用两种方式验证 - **Demo01 运行验证(必做)**:运行 `course/Demo01`,观察输出——先打印 Student 全部 7 个字段(private/public/public static 三种修饰符),再验证 public email 的读取/修改、private name 的暴力反射、static school 的传 null 操作 - **getModifiers 返回 int 理解**:打印 `emailField.getModifiers()` 直接输出一个 int 数字(如 1),用 `Modifier.toString()` 解析后才得到 `public`——理解"修饰符底层是 int 常量值" - **暴力反射实验(必做)**:把 Demo01 中 `nameField.setAccessible(true)` 注释掉再运行,观察抛 **IllegalAccessException**——直观体会"不 setAccessible(true) 无法访问 private 字段"(与上一课访问 private 构造方法完全一致的套路) - **getField 拿私有字段实验**:把 Demo01 中 `clazz.getDeclaredField("name")` 改成 `clazz.getField("name")`,运行观察抛 **NoSuchFieldException**——体会"getField 只能拿 public 字段"的口诀 - **静态属性传 null 理解(必做)**:思考为什么静态属性 get/set 的对象参数要传 null——静态成员(静态属性/静态方法/静态代码块)不属于某个对象,而是属于整个类;对比实例属性必须传对象(不同对象字段值不同) - **静态属性反射修改验证**:观察 `schoolField.set(null, "福建桥梁职业技术学院")` 之后 `Student.school` 直接访问输出新值——体会"反射 set 与普通访问同一份数据、静态字段全类共享" - **与 Constructor 反射对比(必做)**:回顾上一课(08-12 反射 Constructor)——获取 API 都是"含 Declared 拿私有、不含 Declared 只拿 public",访问私有成员都要 `setAccessible(true)`;对比 Constructor(newInstance 创建对象)与 Field(get/set 读写字段)的差异 - **反射三大成员串联(本课完结)**:反射三大核心成员已全部学完——**Constructor**(构造方法,08-12 创建对象)/ **Field**(字段属性,本课读写字段)/ **Method**(方法,本课调用方法)——三者套路完全一致:`getDeclaredXxx` 拿私有 + `setAccessible(true)` 放开权限 + 执行操作 - **getDeclaredFields 遍历练习**:写一个通用工具方法 `printFields(Object obj)`:传入任意对象,用 `getDeclaredFields()` 遍历打印所有字段的修饰符/类型/属性名——体会"反射可以做通用工具"(回顾上一课 printUtil 思路) - **受检异常处理回顾**:观察 Demo01 main 方法签名 `throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException`——getField/getDeclaredField 字段名写错抛 NoSuchFieldException、private 未放开权限操作抛 IllegalAccessException、Class.forName 类名写错抛 ClassNotFoundException——回顾 07-28 异常处理知识 - **Method 概念复述(必做)**:用一句话说清"什么是 Method 反射"——Method 对象代表类中的一个方法,反射通过 `Method.invoke(对象, 参数...)` 调用方法,等价于 `对象.方法(参数)`,方法的名称和参数运行时才确定;能说出应用场景(动态代理、测试框架、注解解释器、框架调用 setter/getter) - **获取 Method 的 4 个 API 背诵(必做)**:`getMethods()`(所有 public 数组,含父类/接口继承如 toString)/ `getMethod(name, parameterTypes)`(指定 public)/ `getDeclaredMethods()`(本类全部含 private,不含父类/接口)/ `getDeclaredMethod(name, parameterTypes)`(指定含 private)——口诀与 Field/Constructor 完全一致:**含 Declared 的能拿私有,不含 Declared 的只能拿 public** - **重载方法区分(必做)**:理解 `getMethod("setName", String.class)` 中的 `String.class` 是**用来区分同名重载方法**的——不写参数类型或写错都会抛 NoSuchMethodException;试着重载一个 `setName(String, Integer)` 并分别用两种签名获取 - **Method 常用方法背诵**:`invoke(obj, 参数...)` 调用方法 / `setAccessible(true)` 暴力反射 / `getReturnType()` 返回值类型 / `getParameterTypes()` 参数类型数组 / `getModifiers()` 修饰符 - **invoke 四种调用规律背诵(必做)**:①实例方法 `invoke(对象, 参数...)` 第一参数传对象 ②**静态方法 `invoke(null, 参数...)` 传 null** ③**void 方法 invoke 返回 null** ④**private 方法必须先 setAccessible(true)**——与 Field 操作静态属性传 null 同一规律 - **Demo02 运行验证(必做)**:运行 `course/Demo02`,观察输出——`getMethods()` 遍历打印所有 public 方法(含 Object 继承的 toString/hashCode/getClass 等)+ Stream 处理参数类型、`getDeclaredMethods()` 输出本类全部方法(含 read/write 私有方法)、`getMethod("getName").invoke(s)` 打印"张三"、`getMethod("setName", String.class).invoke(s, "李四")` 后打印"李四"、暴力反射 `read()` 输出"李四读......."、静态方法 `write()` 输出"静态方法...写..............." - **invoke 调用实例 vs 静态对比实验**:分别用 `getMethod("getName")`(实例)和 `getMethod("getSchool")`(静态)调用——体会实例方法 invoke 第一参数传对象 s、静态方法传 null;把静态方法的 invoke 第一参数误传成对象 s,观察它是否也能执行(static 方法本质不依赖对象,传对象也会被忽略) - **暴力反射私有方法实验(必做)**:把 Demo02 中 `readMethod.setAccessible(true)` 注释掉再运行,观察抛 **IllegalAccessException**——体会"private 方法不放开权限无法 invoke" - **getMethod 拿私有方法实验**:用 `studentClass.getMethod("read")`(不带 Declared)尝试获取 private 方法,观察抛 **NoSuchMethodException**——验证"getMethod 只能拿 public"的口诀 - **void 方法返回值验证**:`setNameMethod.invoke(s, "李四")` 返回 null(void 方法),打印 `System.out.println(setNameMethod.invoke(s, "李四"))` 观察输出 null;对比 `getNameMethod.invoke(s)` 返回 "张三"——体会"无返回值方法 invoke 返回 null" - **getMethods vs getDeclaredMethods 对比**:思考两者的区别——getMethods 拿 public + 父类/接口继承;getDeclaredMethods 拿本类全部(含 private)不含父类。数一数 Student 的 getMethods() 输出里有哪些是 Object 继承来的(toString/hashCode/equals/getClass/notify/wait 等) - **InvocationTargetException 理解**:把 `read()` 方法体内故意写一个会抛异常的语句(如 `int x = 1/0`),反射 `invoke` 调用它,观察抛 **InvocationTargetException**(被调方法内部异常被包装)——回顾 07-28 异常处理知识 - **练习答案参考**:课堂练习四(模拟 ORM mapToObject)的参考答案代码在 `exericse/OrmFieldMapTest.java`(含 `exericse/Student.java` 实体类)——运行观察静态字段传 null 操作 + Map 数据库行映射到 Student 对象的完整流程 --- ## 15. 拓展阅读 - **反射 Field 在 ORM 框架中的落地**:MyBatis、Hibernate 等 ORM 框架的**结果映射(Result Mapping)**就是本课"Field.set() 把数据库数据映射到 Java 类"的经典实现——数据库表字段名对应 Java 实体属性名,查询结果通过反射 Field.set() 逐字段赋值 - **反射三大核心成员(已全部学完)**:反射三大核心成员已全部掌握——**Constructor(构造方法,08-12 newInstance 创建对象)+ Field(字段属性,本课 get/set 读写字段)+ Method(方法,本课 invoke 调用方法)**——三者套路完全一致:`getDeclaredXxx` 拿私有 + `setAccessible(true)` 放开权限 + 执行操作;至此反射三件套(建对象/读写属性/调方法)可组合完成框架级操作 - **Method 反射在动态代理中的落地**:**JDK 动态代理(Proxy + InvocationHandler)**底层就是 Method.invoke——代理对象调用任何方法都会被转发到 `InvocationHandler.invoke(proxy, method, args)`,其中 `method.invoke(target, args)` 反射调用真实对象的方法——这正是本课"Method 是动态代理的基础"论断的经典实现;后续学习 Spring AOP(基于动态代理)时会反复见到 - **Method 反射在测试框架中的落地**:JUnit / TestNG 等**测试框架**通过反射 `getDeclaredMethods()` 遍历类的全部方法,找出带 `@Test` 注解的方法,再 `method.invoke(测试对象)` 执行——"注解 + 反射"是测试框架的底层原理,本课 Demo02 的遍历方法正是这个套路 - **Method 反射在框架 setter/getter 调用中的落地**:Spring 的 BeanUtils / MyBatis 的结果映射都大量使用 `getMethod("set" + 属性名首字母大写, 类型.class)` + `invoke(obj, 值)` 反射调用 setter 赋值——这就是为什么实体类要写标准 getter/setter(本课 Student 新增的 7 组 getter/setter 正是为此准备) - **getParameterTypes 与重载的关联**:反射通过 `getParameterTypes()` 拿到方法的**参数类型数组**(`Class[]`),这与 `getMethod(name, parameterTypes...)` 的传参一一对应——重载方法(同名不同参)在 Class 对象里其实是**多个独立的 Method 对象**,必须靠参数类型定位到具体那一个 - **invoke 的参数匹配与自动装箱**:`invoke(obj, 参数...)` 传入的实际参数会与方法的参数类型匹配(基本类型自动装箱,如 setAge(Integer) 传 int 值 25 会被自动装箱成 Integer);参数类型不匹配或数量不对抛 `IllegalArgumentException` - **InvocationTargetException 的包装含义**:`invoke` 调用方法时,**如果被调方法内部抛异常,invoke 会把异常包装成 `InvocationTargetException` 抛出**(而不是直接抛原异常)——调用者需要 `getCause()` 拿到真正的异常;这是反射调用方法与普通调用的一个隐蔽差异 - **Method.invoke 的 Class.newInstance 对比**:JDK9+ 推荐用 `Constructor.newInstance()`(可传参、可访问 private 构造),废弃了 `Class.newInstance()`(只能无参且权限检查严格)——同理反射调用方法统一用 `Method.invoke` 而非老旧的 `Class.getMethod` 直接裸调 - **反射三件套组合的框架能力**:把本课的知识点组合起来——`getConstructor().newInstance()` 创建对象(08-12)+ `getDeclaredField().set()` 给字段赋值(Field)+ `getDeclaredMethod().invoke()` 调用方法(Method)——就是 MyBatis 结果映射、Spring Bean 初始化、测试框架执行测试用例的完整底层链路 - **getModifiers() 的 int 常量**:Java 反射中修饰符以 **int 位掩码(bitmask)** 表示——public=1、private=2、protected=4、static=8、final=16...`Modifier.toString()` 内部就是按位与运算拼接出字符串;`Modifier.isPublic(int)` / `Modifier.isStatic(int)` 等 isXxx 方法可单独判断某个修饰符是否存在 - **Field.setAccessible 与 JPMS**:`setAccessible(true)` 本质是**绕过 Java 访问控制检查**;在模块化系统(JPMS)中,跨模块访问私有成员还受 `--add-opens` 参数约束——Spring 等框架在 JDK17+ 运行时需要配置 JVM 参数放开模块限制 - **反射的缓存优化**:反射 `getField`/`getDeclaredField` 本身有开销,框架(如 MyBatis)通常**在启动阶段一次性获取并缓存 Field 对象**,运行时只调用 `get()/set()`——这是"框架用反射但性能不差"的关键优化手段 - **setAccessible 的性能影响**:`setAccessible(true)` 不仅放开权限,还会让 JVM 为字段访问做**更快的内部优化**(绕过访问检查的反射调用性能显著提升)——所以框架总是对私有字段先 setAccessible(true) 再操作,既是"能访问"也是"更高效" - **字段名与数据库列名的映射**:ORM 框架中实体属性名(如 `userName`)与数据库列名(如 `user_name`)往往不一致——框架通过配置或约定(驼峰↔下划线转换)拿到字段名后,用反射 `Field.set()` 赋值,这正是"配置文件/注解驱动反射"的典型场景 - **IDEA 调试器的实现原理**:IDEA 等 IDE 的**调试器变量查看窗口**能在运行时显示对象的所有字段值(包括 private)——底层就是反射 `getDeclaredFields()` + `setAccessible(true)` + `field.get(对象)`,与本课 Demo01 的演示逻辑一致 - **JSON 序列化框架**:Jackson / Gson / Fastjson 把 Java 对象转成 JSON 字符串、或把 JSON 转回对象,底层也大量使用 Field 反射——读取对象所有字段名与值(序列化)、按 JSON 键名给对象字段赋值(反序列化) - **反射破坏封装性的辩证关系**:本课 Field 反射可以访问 private 字段、修改 final 字段,既是框架能力的来源,也是"破坏封装性"的体现——日常业务代码中应遵守上一课的原则:**正常业务用普通方式访问属性,反射留给框架与中间件** - **JDK 文档**:[`java.lang.Class`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Class.html)(getFields/getField/getDeclaredFields/getDeclaredField + getMethods/getMethod/getDeclaredMethods/getDeclaredMethod)、[`java.lang.reflect.Field`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/reflect/Field.html)(get/set/setAccessible/getType/getName/getModifiers)、[`java.lang.reflect.Method`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/reflect/Method.html)(invoke/setAccessible/getReturnType/getParameterTypes/getModifiers)、[`java.lang.reflect.Modifier`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/reflect/Modifier.html)(toString/isPublic/isStatic 等修饰符解析工具类)