|
@@ -0,0 +1,498 @@
|
|
|
|
|
+# 20260813 课堂笔记 — 反射 Field 属性/字段/变量
|
|
|
|
|
+
|
|
|
|
|
+- **日期**:2026-08-13
|
|
|
|
|
+- **项目**:`c260813`
|
|
|
|
|
+- **包路径**:`course`
|
|
|
|
|
+- **作者**:WanJL
|
|
|
|
|
+- **内容概览**:反射 Field(属性/字段/变量)(Demo01 演示:Class 类获取 Field 对象的 4 个 API → 获取所有属性并输出修饰符和类型 → 读取/修改 public 属性 → setAccessible(true) 暴力反射读取/修改 private 属性 → 操作静态属性传 null)+ Student 实体类(private/public/public static 字段设计 + 4 个构造方法)
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 目录
|
|
|
|
|
+
|
|
|
|
|
+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. [知识点全景总结](#9-知识点全景总结)
|
|
|
|
|
+10. [随堂练习要点](#10-随堂练习要点)
|
|
|
|
|
+11. [拓展阅读](#11-拓展阅读)
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 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<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()` 修改:
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+// 来源: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)` **临时取消访问权限检查**——这就是**暴力反射**。
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+// 来源: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**。
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+// 来源: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 的三段演示:
|
|
|
|
|
+
|
|
|
|
|
+```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;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ 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 创建对象用) |
|
|
|
|
|
+
|
|
|
|
|
+> **要点**:
|
|
|
|
|
+> - **字段刻意混合 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. 知识点全景总结
|
|
|
|
|
+
|
|
|
|
|
+| 知识点 | 核心内容 | 来源 |
|
|
|
|
|
+|--------|----------|------|
|
|
|
|
|
+| **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.study()** | Student 的实例方法 `输出 name+"在学习....."` | course/Student.java |
|
|
|
|
|
+| **受检异常** | main 方法签名 throws `ClassNotFoundException, NoSuchFieldException, IllegalAccessException`——getField 字段名不存在抛 NoSuchFieldException、private 未放开权限 get/set 抛 IllegalAccessException、forName 类名错误抛 ClassNotFoundException | course/Demo01 main |
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 10. 随堂练习要点
|
|
|
|
|
+
|
|
|
|
|
+- **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**(创建对象)和 **Field**(读写字段属性),下一个核心成员是 **Method**(调用方法,`getMethod` + `invoke`)——套路与两者完全一致,提前预习
|
|
|
|
|
+- **getDeclaredFields 遍历练习**:写一个通用工具方法 `printFields(Object obj)`:传入任意对象,用 `getDeclaredFields()` 遍历打印所有字段的修饰符/类型/属性名——体会"反射可以做通用工具"(回顾上一课 printUtil 思路)
|
|
|
|
|
+- **受检异常处理回顾**:观察 Demo01 main 方法签名 `throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException`——getField/getDeclaredField 字段名写错抛 NoSuchFieldException、private 未放开权限操作抛 IllegalAccessException、Class.forName 类名写错抛 ClassNotFoundException——回顾 07-28 异常处理知识
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 11. 拓展阅读
|
|
|
|
|
+
|
|
|
|
|
+- **反射 Field 在 ORM 框架中的落地**:MyBatis、Hibernate 等 ORM 框架的**结果映射(Result Mapping)**就是本课"Field.set() 把数据库数据映射到 Java 类"的经典实现——数据库表字段名对应 Java 实体属性名,查询结果通过反射 Field.set() 逐字段赋值
|
|
|
|
|
+- **反射三大核心成员进度**:本课完成了反射三大核心成员中的 **Constructor(构造方法,08-12)+ Field(字段属性,本课)**,还剩 **Method(方法)**——下一课将学习 `getMethods()` / `getDeclaredMethods()` / `getMethod(name, 参数类型)` + `method.invoke(对象, 参数)` 调用方法,套路与本课完全一致(Declared 拿私有 + setAccessible(true) 放开权限)
|
|
|
|
|
+- **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)、[`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.Modifier`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/reflect/Modifier.html)(toString/isPublic/isStatic 等修饰符解析工具类)
|
|
|
|
|
+
|