20260813-笔记.md 73 KB

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 调用实例方法/带参方法/私有方法/静态方法)+ 反射知识全景总览(Demo03:把整个反射体系整理成 4 类知识地图——Class 类反射(成员属性/方法/构造)、Field 属性反射、Method 方法反射、Constructor 构造反射)+ Student 实体类(private/public/public static 字段设计 + 4 个构造方法 + 7 组 getter/setter + 私有方法 read/write)

目录

  1. 反射 Field 概述:成员变量的反射操作
  2. 获取 Field 对象的 4 个 API:Declared 决定能否拿到私有字段
  3. Field 类的常用方法:get/set/setAccessible/getType/getName/getModifiers
  4. Demo01 演示①:获取所有属性并输出修饰符和类型
  5. Demo01 演示②:读取/修改 public 属性
  6. Demo01 演示③:读取/修改 private 属性——暴力反射 setAccessible(true)
  7. Demo01 演示④:操作静态属性——对象参数传 null
  8. Student 实体类解析:三种字段权限的反射目标
  9. 反射 Method 方法概述:方法的反射操作(Demo02)
  10. 获取 Method 对象的 4 个 API:Declared 与重载参数
  11. Method 类的常用方法:invoke/setAccessible/getReturnType/getParameterTypes/getModifiers
  12. Demo02 演示:遍历方法 + invoke 调用实例/带参/私有/静态方法
  13. 反射知识全景总览(Demo03):四类反射的完整知识地图
  14. 知识点全景总结
  15. 随堂练习要点
  16. 拓展阅读

1. 反射 Field 概述:成员变量的反射操作

概念 —— Field 对象代表类中的一个成员变量

Field 对象代表类中的一个成员变量(属性/字段)。反射可以通过 Field 的 get() 读取字段值、set() 修改字段值。字段的反射操作在框架中应用极广:

// 来源: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 套路完全一致):

// 来源: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 对象后可以做什么

// 来源: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 校验)非常常用。

// 来源: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

5. Demo01 演示②:读取/修改 public 属性

概念 —— getField + get/set 操作公有字段

对于 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))

6. Demo01 演示③:读取/修改 private 属性——暴力反射 setAccessible(true)

概念 —— getDeclaredField + setAccessible(true) 突破私有权限

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)的暴力反射操作模式相同

7. Demo01 演示④:操作静态属性——对象参数传 null

概念 —— 静态成员属于整个类,操作时不传对象传 null

静态成员(静态属性、静态方法、静态代码块)不属于某个对象,而是属于整个类的。所以反射操作静态属性时,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)

8. Student 实体类解析:三种字段权限的反射目标

概念 —— 一个覆盖了 public/private/static 的反射目标类

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, 值) 操作(演示④)

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)的调用目标:

// 来源: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)的最后一位,也是动态代理、测试框架、注解解释器等高级功能的基础。

// 来源: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 对象的四个方法

// 来源: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 字段)

重载方法必须指明参数类型

// 来源: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 对象后可以做什么

// 来源: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 的四种调用规律(重点)

// 来源: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 静态方法。

// 来源: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 调用静态方法,规律统一

13. 反射知识全景总览(Demo03):四类反射的完整知识地图

概念 —— 把整个反射体系整理成 4 类知识地图

学完了反射的三大核心成员(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()

记忆要点:三组 12 个获取 API + 每类对象的关键方法

① 获取 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 共用同一条口诀

② 每类反射对象的关键方法:

  • Field 看"属性值":属性有三部分——名字 getName()、修饰符 getModifiers()、值 get() / set()
  • Method 看"执行":方法有五部分——名字 getName()、执行 invoke()、返回值类型 getReturnType()、参数类型 getParameterTypes()、修饰符 getModifiers()
  • Constructor 看"创建":构造方法有三部分——修饰符 getModifiers()、参数类型 getParameterTypes()、创建对象 newInstance()
  • 三个反射对象共有的 API:Field/Method/Constructor 都有 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() 动态计算参数类型数组)

14. 知识点全景总结

知识点 核心内容 来源
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

15. 随堂练习要点

  • 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 对象的完整流程
  • 反射知识全景背诵(必做):把 Demo03 的 4 类知识地图背下来——①Class 类反射(成员属性/方法/构造)②Field 属性反射(名字/修饰符/值)③Method 方法反射(名字/执行/返回值/参数/修饰符)④Constructor 构造反射(修饰符/参数/创建对象)——能不看笔记默写出来
  • 12 个获取 API 默写(必做):给"属性/方法/构造"各自默写 4 个获取方法(数组/单个/含私有数组/含私有单个)——getFields/getField/getDeclaredFields/getDeclaredField(属性)、getMethods/getMethod/getDeclaredMethods/getDeclaredMethod(方法)、getConstructors/getConstructor/getDeclaredConstructors/getDeclaredConstructor(构造)——体会"三组 12 个 API 规律完全一致"
  • 口诀统一性验证:对照 12 个获取 API,逐个确认"含 Declared 的能拿私有、不含 Declared 的只能拿 public"对属性/方法/构造三组都成立——这是反射 API 的统一规律
  • 反射对象共有 API 对比(必做):列出 Field/Method/Constructor 三者共同有的方法(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) 方法
  • Demo03 无 main 方法理解:观察 Demo03 类只有大段注释没有 main 方法——体会"注释也能承载知识点":反射知识地图以注释形式存在,需要时打开 Demo03.java 即可总览全部反射 API

16. 拓展阅读

  • 反射 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 字段,既是框架能力的来源,也是"破坏封装性"的体现——日常业务代码中应遵守上一课的原则:正常业务用普通方式访问属性,反射留给框架与中间件
  • 反射 API 的"总索引"价值:Demo03 的类注释相当于一张反射 API 速查表——面试或写框架代码时"忘了某个 API 名字",打开 Demo03.java 就能看到 4 类反射的全部方法(Class 的 12 个获取方法 + Field/Method/Constructor 各自的组成部分方法);这种"用注释承载知识地图"的做法也适合自己整理学习笔记
  • 12 个获取 API 与"组合拳"思维:反射 3 个成员(Field/Method/Constructor)× 4 种获取方式(数组/单个/含私有数组/含私有单个)= 12 个 API,规律完全一致——记住"每个成员 4 个获取方法 + Declared 口诀"就能举一反三,这也是面试常考的"反射 API 全家桶"
  • Constructor 反射的补充方法:Demo03 提到 Constructor 也有 getModifiers()(构造修饰符)和 getParameterTypes()(构造参数类型)——上一课(08-12)主要用了 newInstance() 创建对象,这两个方法是构造反射的补充维度,可在练习中试用(打印构造方法的修饰符与参数类型)
  • 练习答案中的通用工具设计:练习五答案用"遍历 + setAccessible + Modifier.toString + 字符串修复"打印方法信息、练习六答案用"args[i].getClass() 动态计算参数类型"实现通用 invoke——这两种技巧(字符串清理、动态参数类型推导)是写反射通用工具的常用套路,后续做框架级工具时可直接复用
  • JDK 文档: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 等修饰符解析工具类)