【部分代码涉及io流与集合的知识点,后续会补】
1. 反射
1.1 反射的概念

- 反射 :指程序在运行状态中,可以对任意一个类的结构信息 进行动态获取 ,并可以对对象的属性和方法 进行动态调用。
反射的关键价值不在于"绕过正常调用方式",而在于把原本编译期确定的类型访问,转化为运行期可决策、可配置、可扩展的元数据访问。换言之,反射使程序能够在不知道某个类的具体编译期类型时,仍然通过类名、字段名、方法名等信息完成对象创建与行为调用。
核心 :反射强调的是运行期结构解析能力 。它把
Class、Constructor、Field、Method等元对象暴露给程序,使程序可以像处理普通对象一样处理类的结构信息。
1.2 核心前置:Java文件、字节码文件与字节码对象
在正式学习反射的 API 之前,必须厘清三个递进的核心概念:
.java源文件:程序员日常编写的普通 Java 代码文件。.class字节码文件 :.java文件经过编译器编译后生成的二进制文件(物理存在于硬盘上,通过文件管理器肉眼可见)。Class字节码文件对象 :当 JVM(Java虚拟机)运行并需要使用某个类时,会将对应的.class文件加载到内存中,并自动为其创建一个对象。这个对象内部包含了该类的所有核心结构:构造方法、成员变量、成员方法。
重点结论:
- 反射获取的到底是什么? 反射操作的根本目标,就是这个存在于内存中的字节码文件对象。
- 唯一性 :由于同一个类的
.class字节码文件在一次程序运行过程中只会被 JVM 加载一次,因此这个字节码文件对象在内存中是绝对唯一的。
1.3 学习反射到底学什么

理解了字节码文件对象后,反射就不再是抽象的概念了。它其实就是围绕这个唯一的 Class 字节码对象展开的一组结构化操作。
- 获取
Class字节码对象(一切反射的入口)。- 获取构造方法,并通过构造方法创建对象。
- 获取成员变量,并完成赋值或取值。
- 获取成员方法,并完成动态调用。
1.4 获取 Class 对象的三种方式

1.4.1 核心 API
| 获取方式 | 典型写法 | 适用场景 |
|---|---|---|
Class.forName("全类名") |
Class.forName("reflection.one.Student") |
最常用于配置文件驱动 |
类名.class |
Student.class |
适合已知类型的静态引用 |
对象.getClass() |
s.getClass() |
适合已存在对象时获取真实运行时类型 |
注意 :正如 1.2 节所强调的,同一个
.class字节码文件在一次程序运行中只会被加载一次,因此这三种方式最终获得的都是内存中同一个绝对唯一的Class对象。
1.4.2 代码示例

1.4.2.1 Student
java
package reflection.one;
/**
* 学生实体类,用于演示反射操作。
* 包含基本属性、构造方法、Getter/Setter 以及 toString 方法。
*/
public class Student {
private String name;
private int age;
/**
* 无参构造方法。
* 规范:在使用反射创建对象时(如 Class.newInstance()),通常依赖无参构造方法,因此必须显式保留。
*/
public Student() {
}
/**
* 全参构造方法。
*
* @param name 学生姓名
* @param age 学生年龄
*/
public Student(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取学生姓名。
*
* @return name 当前学生的姓名
*/
public String getName() {
return name;
}
/**
* 设置学生姓名。
*
* @param name 要设置的姓名
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取学生年龄。
*
* @return age 当前学生的年龄
*/
public int getAge() {
return age;
}
/**
* 设置学生年龄。
*
* @param age 要设置的年龄
*/
public void setAge(int age) {
this.age = age;
}
/**
* 重写 toString 方法,方便在控制台打印对象的具体属性值,而非内存地址。
*
* @return 包含学生姓名和年龄的格式化字符串
*/
@Override
public String toString() {
return "Student{name = '" + name + "', age = " + age + "}";
}
}
1.4.2.2 MyReflectDemo1
java
package reflection.one;
/**
* 反射演示类:展示获取 Class 字节码对象的三种常见方式。
*/
public class MyReflectDemo1 {
public static void main(String[] args) throws ClassNotFoundException {
/*
* 获取 Class 对象的三种方式核心总结:
* 1. Class.forName("全类名");
* 2. 类名.class;
* 3. 对象.getClass();
*
* 核心原理:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载到内存中一次。
* 因此,无论通过哪种方式获取的 Class 对象,指向的都是内存中的同一个实例。
*/
/*
* 【细节拓展】为什么要写 Class<?> 而不是直接写 Class?
* 1. 自 JDK 1.5 起,Class 被设计为泛型类 Class<T>。
* 2. <?> 是 Java 泛型中的"无界通配符",代表"某种未知的具体类型"。
* 3. 规范要求:如果只写 Class(原始类型),IDE 会报黄色警告(Raw Use)。
* 加上 <?> 可以消除警告,明确告诉编译器:"我知道这里有泛型,但我目前不需要限定具体的类型",从而提升代码的严谨性。
*/
// 1. 第一种方式:Class.forName("全类名")
// 全类名 = 包名 + 类名
// 应用场景:最为常用。通常配合配置文件使用,将类名提取到配置文件中,通过读取字符串来动态加载类,从而实现解耦。
Class<?> clazz1 = Class.forName("reflection.one.Student");
// 2. 第二种方式:类名.class
// 应用场景:一般更多的是当作参数进行传递。例如在锁对象 synchronized(Student.class) 或传递类型参数时使用。
Class<?> clazz2 = Student.class;
// 3. 第三种方式:对象.getClass()
// 应用场景:当我们已经创建了该类的对象实例时才可以使用。通常用于多态场景下,判断传入的对象的真实运行时类型。
Student s = new Student();
Class<?> clazz3 = s.getClass();
// 验证结果:比较三个 Class 对象的内存地址是否相同
System.out.println("clazz1 == clazz2 : " + (clazz1 == clazz2)); // 预期输出: true
System.out.println("clazz2 == clazz3 : " + (clazz2 == clazz3)); // 预期输出: true
}
}

总结 :
Class对象是反射体系的入口。没有Class,后续的构造器、字段、方法都无法被定位。
1.5 获取构造方法
构造方法反射的目标,是让程序在运行期根据结构信息创建对象。尤其当目标类型来自配置文件、插件扫描或框架容器时,程序无法在源码中直接 new 出固定对象,此时就必须通过 Constructor 完成实例化。
1.5.1 核心 API
- Class 类中用于【获取】构造方法
| 方法声明 | 功能核心说明 |
|---|---|
Constructor<?>[] getConstructors() |
返回本类中所有被 public 修饰的构造方法(数组) |
Constructor<?>[] getDeclaredConstructors() |
返回本类中所有的 构造方法(数组),包含 private、protected 及默认权限 |
Constructor<T> getConstructor(Class<?>... parameterTypes) |
根据参数类型,精准返回指定的 public 构造方法(单个) |
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) |
根据参数类型,精准返回指定的任意权限构造方法(单个) |
- Constructor 类中用于【操作与内省】构造方法
| 方法声明 | 功能核心说明 |
|---|---|
void setAccessible(boolean flag) |
【权限控制】 传入 true 时,强行取消 Java 的访问权限检查(即"暴力反射"必备) |
T newInstance(Object... initargs) |
【对象实例化】 传入实际参数,执行该构造方法来创建并返回对象实例 |
int getModifiers() |
【内省元数据】 获取该构造方法的修饰符 (返回底层整数,需配合 Modifier 解析) |
Parameter[] getParameters() |
【内省元数据】 获取该构造方法的所有参数对象(返回数组,可借此进一步获取参数名和类型) |
String getName() |
【内省元数据】 获取该构造方法的名称(注:构造方法名称固定与全类名一致) |
1.5.2 代码示例

1.5.2.2 Student
java
package reflection.two;
/**
* 学生实体类,用于演示更深入的反射操作。
* 特意设计了不同访问权限 的构造方法。
*/
public class Student {
// ===== 成员变量 =====
private String name;
private int age;
// ===== 构造方法 =====
/**
* public 无参构造
*/
public Student() {
}
/**
* public 单参构造
*/
public Student(String name) {
this.name = name;
}
/**
* protected 单参构造
*/
protected Student(int age) {
this.age = age;
}
/**
* private 双参构造(外部无法直接 new,必须依靠反射 + 暴力破解)
*/
private Student(String name, int age) {
this.name = name;
this.age = age;
}
// ===== Getters / Setters / toString =====
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
@Override
public String toString() {
return "Student{name = '" + name + "', age = " + age + "'}";
}
}
1.5.2.1 ConstructorReflectDemo
java
package reflection.two;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
/**
* 反射演示类:利用反射获取并操作【构造方法】 (Constructor)
* * =====================【核心 API 总结】=====================
* * 1. Class 类中用于获取构造方法的方法:
* - Constructor<?>[] getConstructors() : 获取当前类中所有的 public 构造方法。
* - Constructor<?>[] getDeclaredConstructors() : 获取当前类中所有的构造方法(含 public、protected、默认、private)。
* - Constructor<T> getConstructor(Class<?>... parameterTypes) : 获取当前类中指定的 public 构造方法。
* - Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes): 获取当前类中指定的任意权限的构造方法。
* * 2. Constructor 类中用于【操作与内省】构造方法的方法:
* - void setAccessible(boolean flag) : 设置为 true 可临时取消 Java 语言访问权限检查(即"暴力反射"必备)。
* - T newInstance(Object... initargs) : 根据指定的参数,调用此构造方法创建对象实例。
* - int getModifiers() : 【内省】获取该构造方法的修饰符(返回底层整数,需配合 Modifier 解析)。
* - Parameter[] getParameters() : 【内省】获取该构造方法的所有参数对象(Parameter 数组)。
* * =========================================================
*/
public class ConstructorReflectDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 1. 获取 Class 字节码文件对象
Class<?> clazz = Class.forName("reflection.two.Student");
// ================= 获取多个构造方法 =================
// 2.1 获取目标类中所有被 public 关键字修饰的构造方法
System.out.println("--- 1. 测试 getConstructors() ---");
Constructor<?>[] cons1 = clazz.getConstructors();
for (Constructor<?> con : cons1) {
System.out.println(con); // 预期:只能打印出 public 的构造方法
}
// 2.1 获取目标类中所有构造方法
System.out.println("\n--- 2. 测试 getDeclaredConstructors() ---");
Constructor<?>[] cons2 = clazz.getDeclaredConstructors();
for (Constructor<?> con : cons2) {
System.out.println(con); // 预期:能打印出所有的构造方法 (包含 private 等)
}
// ================= 获取指定的单个构造方法 =================
System.out.println("\n--- 3. 测试获取指定的单个构造方法 ---");
// 3.1 获取无参构造
Constructor<?> con1 = clazz.getDeclaredConstructor();
System.out.println("获取无参: " + con1);
// 3.2 获取带一个 String 参数的构造
Constructor<?> con2 = clazz.getDeclaredConstructor(String.class);
System.out.println("获取单参(String): " + con2);
// 3.3 获取带一个 int 参数的构造
Constructor<?> con3 = clazz.getDeclaredConstructor(int.class);
System.out.println("获取单参(int): " + con3);
// ================= 核心操作:内省与暴力反射 =================
System.out.println("\n--- 4. 深入操作 private 双参构造 ---");
// 目标:获取 private Student(String name, int age)
// 注意:因为目标构造方法是 private 的,所以必须调用带 Declared 的方法
Constructor<?> con4 = clazz.getDeclaredConstructor(String.class, int.class);
// 4.1 获取权限修饰符
int modifiers = con4.getModifiers();
System.out.println("权限修饰符: " + Modifier.toString(modifiers) + " (对应整数: " + modifiers + ")");
// 4.2 获取该构造方法的参数列表
Parameter[] parameters = con4.getParameters();
System.out.println("参数列表:");
for (Parameter parameter : parameters) {
System.out.println(" - " + parameter);
}
/*
* 【核心细节:暴力反射】
* 由于 con4 是 private 修饰的私有构造,正常情况下外部类无权调用。
* 调用 setAccessible(true) 表示"忽略访问控制权限",强行访问。
* 警告:如果不加这一句,直接调用 newInstance 会抛出 IllegalAccessException(非法访问异常)。
*/
con4.setAccessible(true);
// 4.3 通过反射执行该构造方法,传入实际参数,创建实例对象
Student stu = (Student) con4.newInstance("张三", 23);
System.out.println("\n暴力反射创建对象成功: " + stu);
}
}

总结 :
getDeclaredConstructor负责"拿到",setAccessible(true)负责"允许访问",newInstance负责"真正创建"。
1.6 获取成员变量
成员变量反射用于读取和修改对象内部状态 。它常用于通用数据导出、对象映射、序列化框架、依赖注入等场景。
1.6.1 核心 API
- Class 类中用于【获取】成员变量
| 方法声明 | 功能核心说明 |
|---|---|
Field[] getFields() |
返回所有被 public 修饰的成员变量(数组) |
Field[] getDeclaredFields() |
返回所有的 成员变量(数组),包含 private、protected 及默认权限 |
Field getField(String name) |
根据变量名,返回指定的 public 成员变量(单个) |
Field getDeclaredField(String name) |
根据变量名,返回指定的任意权限成员变量(单个) |
- Field 类中用于【操作】成员变量
| 方法声明 | 功能核心说明 |
|---|---|
void setAccessible(boolean flag) |
【权限控制】 传入 true 时,强行取消 Java 的访问权限检查("暴力反射"必调) |
void set(Object obj, Object value) |
【数据操作】给指定对象 obj 的该成员变量赋值 为 value |
Object get(Object obj) |
【数据操作】 获取 指定对象 obj 中该成员变量记录的值 |
Class<?> getType() |
【内省元数据】获取该成员变量的数据类型 (如 String.class、int.class) |
String getName() |
【内省元数据】获取该成员变量的名称 |
1.6.2 代码示例

1.6.2.1 Student
java
package reflection.three;
/**
* 标准的 JavaBean 实体类 (gender 特意使用了 public)
*/
public class Student {
// ================= 1. 成员变量私有化 =================
private String name;
private int age;
public String gender;
// ================= 2. 构造方法 =================
/**
* 无参构造
*/
public Student() {
}
/**
* 全参构造
*/
public Student(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
// ================= 3. 公共的 Getter 和 Setter 方法 =================
/**
* 获取姓名
* @return name 当前学生的姓名
*/
public String getName() {
return name;
}
/**
* 设置姓名
* @param name 要设置的姓名
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取年龄
* @return age 当前学生的年龄
*/
public int getAge() {
return age;
}
/**
* 设置年龄
* @param age 要设置的年龄
*/
public void setAge(int age) {
this.age = age;
}
/**
* 获取性别
* @return gender 当前学生的性别
*/
public String getGender() {
return gender;
}
/**
* 设置性别
* @param gender 要设置的性别
*/
public void setGender(String gender) {
this.gender = gender;
}
// ================= 其他方法 =================
/**
* 重写 toString 方法,方便在控制台直观地打印对象的属性值
*/
@Override
public String toString() {
return "Student{name = '" + name + "', age = " + age + ", gender = '" + gender + "'}";
}
}
1.6.2.2 FieldReflectDemo
java
package reflection.three;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
/**
* 反射演示类:利用反射获取并操作【成员变量】 (Field)
* * =====================【核心 API 总结】=====================
* * 1. Class 类中用于获取成员变量的方法:
* - Field[] getFields() : 返回所有 public 成员变量对象的数组(包括父类的)。
* - Field[] getDeclaredFields() : 返回所有成员变量对象的数组(仅限本类,包含 private、protected 等,不含父类)。
* - Field getField(String name) : 返回指定的 public 成员变量对象。
* - Field getDeclaredField(String name) : 返回指定的任意权限的成员变量对象。
* * 2. Field 类中用于【操作与内省】变量的方法:
* - void setAccessible(boolean flag) : 设置为 true 可临时取消 Java 语言访问权限检查(即"暴力反射"必备)。
* - void set(Object obj, Object value) : 将指定对象 (obj) 中该成员变量的值设置为 value。
* - Object get(Object obj) : 获取指定对象 (obj) 中该成员变量的值。
* - Class<?> getType() : 【内省】获取该成员变量的数据类型(如 String.class)。
* - String getName() : 【内省】获取该成员变量的名称。
* - int getModifiers() : 【内省】获取该成员变量的修饰符(返回底层整数,需配合 Modifier 解析)。
* * =========================================================
*/
public class FieldReflectDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
// 1. 获取 Class 字节码文件对象
Class<?> clazz = Class.forName("reflection.three.Student");
// ================= 获取所有成员变量 =================
System.out.println("--- 获取所有的成员变量 ---");
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
System.out.println(field);
}
// ================= 获取单个成员变量并内省 =================
System.out.println("--- 1. 获取指定的单个成员变量 ---");
// 目标:获取 private String name;
Field nameField = clazz.getDeclaredField("name");
System.out.println("获取到的 Field 对象: " + nameField);
// 1.1 获取权限修饰符
int modifiers = nameField.getModifiers();
System.out.println("权限修饰符: " + Modifier.toString(modifiers) + " (对应整数: " + modifiers + ")");
// 1.2 获取成员变量的名字
String n = nameField.getName();
System.out.println("成员变量的名字: " + n);
// 1.3 获取成员变量的数据类型
Class<?> type = nameField.getType();
System.out.println("成员变量的数据类型: " + type);
// ================= 核心操作:读取与修改对象中的值 =================
System.out.println("\n--- 2. 操作对象中记录的值 ---");
// 准备一个实际的对象实例,用于提取和修改数据
Student s = new Student("zhangsan", 23, "男");
System.out.println("原始对象: " + s);
/*
* 【核心细节:暴力反射】
* 因为 name 字段是 private 的,外部类无法直接读写。
* 必须通过 setAccessible(true) 取消权限校验。
*/
nameField.setAccessible(true);
// 2.1 获取值:调用 get 方法,传入要提取数据的对象实例
String value = (String) nameField.get(s);
System.out.println("通过反射提取到的 name 值: " + value);
// 2.2 修改值:调用 set 方法,参数一:要修改的对象实例;参数二:要赋的新值
nameField.set(s, "lisi");
System.out.println("通过反射修改后的对象: " + s);
}
}

注意:字段反射可以突破封装边界,因此在工程实践中应控制使用范围。反射不是替代面向对象封装的常规手段,而是框架层、工具层处理通用对象结构的底层机制。
1.7 获取成员方法
成员方法反射的本质,是把普通方法调用转换为运行期的元数据调用。它允许程序先定位方法对象,再通过 invoke 进行执行。
1.7.1 核心 API
- Class 类中用于【获取】成员方法
| 方法声明 | 功能核心说明 |
|---|---|
Method[] getMethods() |
返回所有被 public 修饰 的成员方法(数组),包含父类继承的公共方法 |
Method[] getDeclaredMethods() |
返回本类中所有的 成员方法(数组),包含私有方法,但不包含继承的方法 |
Method getMethod(String name, Class<?>... parameterTypes) |
根据方法名和参数类型,精准返回指定的 public 成员方法(单个) |
Method getDeclaredMethod(String name, Class<?>... parameterTypes) |
根据方法名和参数类型,精准返回指定的任意权限成员方法(单个) |
- Method 类中用于【运行与内省】成员方法
| 方法声明 | 功能核心说明 |
|---|---|
void setAccessible(boolean flag) |
【权限控制】 传入 true 时,强行取消 Java 的访问权限检查("暴力反射"必备) |
Object invoke(Object obj, Object... args) |
【动态运行】 执行指定方法。 • 参数一 (obj) :用哪个对象去调用该方法 • 参数二 (args) :调用方法传递的实际参数(没有就不写) • 返回值:方法执行后的返回值(没有就不写或返回 null) |
int getModifiers() |
【内省元数据】获取该方法的修饰符 (返回底层整数,需配合 Modifier 解析) |
String getName() |
【内省元数据】 获取该方法的名称 |
Parameter[] getParameters() |
【内省元数据】 获取该方法的所有参数对象 (返回 Parameter 数组) |
Class<?>[] getExceptionTypes() |
【内省元数据】 获取该方法声明抛出的所有异常类型 (返回 Class 数组) |
面试常考避坑指南:
getMethods()会顺藤摸瓜把父类(比如Object类中的wait、equals、hashCode等)所有的 public 方法都抓取出来。而getDeclaredMethods()则非常专一,只抓取当前类自己写的代码,哪怕是 private 也抓,但绝对不抓父类的。
1.7.2 代码示例
1.7.2.1 MyReflectDemo
java
package reflection.four;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
/**
* 反射演示类:利用反射获取并操作【成员方法】 (Method)
* * =====================【核心 API 总结】=====================
* * 1. Class 类中用于获取成员方法的方法:
* - Method[] getMethods() : 返回所有公共成员方法对象的数组,包括继承的。
* - Method[] getDeclaredMethods() : 返回所有成员方法对象的数组,不包括继承的。
* - Method getMethod(String name, Class<?>... parameterTypes) : 返回指定的公共成员方法对象。
* - Method getDeclaredMethod(String name, Class<?>... parameterTypes): 返回指定的任意权限成员方法对象。
* * 2. Method 类中用于【运行与内省】方法的方法:
* - void setAccessible(boolean flag) : 设置为 true 可临时取消 Java 语言访问权限检查(即"暴力反射"必备)。
* - Object invoke(Object obj, Object... args) : 运行方法。
* > 参数一 (obj): 用 obj 对象调用该方法。
* > 参数二 (args): 调用方法的传递的实际参数(如果没有就不写)。
* > 返回值: 方法的返回值(如果没有就不写,或者返回 null)。
* - int getModifiers() : 【内省】获取该方法的修饰符(返回底层整数,需配合 Modifier 解析)。
* - String getName() : 【内省】获取该方法的名字。
* - Parameter[] getParameters() : 【内省】获取该方法的所有参数对象(Parameter 数组)。
* - Class<?>[] getExceptionTypes() : 【内省】获取该方法声明抛出的所有异常类型数组。
* * =========================================================
*/
public class MyReflectDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// 1. 获取 Class 字节码文件对象
Class<?> clazz = Class.forName("reflection.four.Student");
// ================= 获取所有的成员方法 (包含父类公共方法) =================
System.out.println("获取所有的成员方法 (包含父类公共方法): ");
Method[] methods1 = clazz.getMethods();
for (Method method : methods1) {
System.out.println(method);
}
// ================= 获取本类所有的成员方法 (不含父类,含私有) =================
System.out.println("获取本类所有的成员方法 (不含父类,含私有): ");
Method[] methods2 = clazz.getDeclaredMethods();
for (Method method : methods2) {
System.out.println(method);
}
// ================= 获取指定的单一方法并进行内省 =================
System.out.println("--- 1. 获取指定的单个方法 ---");
// 目标:获取 private String eat(String something) throws IOException, NullPointerException
Method m = clazz.getDeclaredMethod("eat", String.class);
System.out.println("获取到的 Method 对象: " + m);
// 1.1 获取方法的修饰符
int modifiers = m.getModifiers();
System.out.println("权限修饰符: " + Modifier.toString(modifiers));
// 1.2 获取方法的名字
String name = m.getName();
System.out.println("方法的名字: " + name);
// 1.3 获取方法的形参
System.out.println("方法的形参列表:");
Parameter[] parameters = m.getParameters();
for (Parameter parameter : parameters) {
System.out.println(" - " + parameter);
}
// 1.4 获取方法的抛出的异常
System.out.println("方法抛出的异常类型:");
Class<?>[] exceptionTypes = m.getExceptionTypes();
for (Class<?> exceptionType : exceptionTypes) {
System.out.println(" - " + exceptionType);
}
// ================= 核心操作:方法运行 (invoke) =================
System.out.println("\n--- 2. 方法运行 ---");
Student s = new Student();
// 因为是 private 方法,必须进行暴力反射
m.setAccessible(true);
// 参数一 s: 表示方法的调用者
// 参数二 "汉堡包": 表示在调用方法的时候传递的实际参数
// 返回值强转:因为我们已知返回值是 String 类型,所以可以直接强转接收
String result = (String) m.invoke(s, "汉堡包");
System.out.println("方法的返回值: " + result);
}
}
1.7.2.2 Student
java
package reflection.four;
import java.io.IOException;
/**
* 学生实体类,专门用于演示更深入的反射操作(主要测试成员方法 Method 的反射)。
*/
public class Student {
// ================= 成员变量 =================
private String name;
private int age;
// ================= 构造方法 =================
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// ================= 成员方法 (专门用于反射测试) =================
public void sleep() {
System.out.println("睡觉");
}
/**
* 【重点修改】
* 1. 权限为 private
* 2. 返回值改为了 String
* 3. 声明抛出了两个异常,用于测试反射获取异常类型
*/
private String eat(String something) throws IOException, NullPointerException {
System.out.println("在吃" + something);
return "奥利给";
}
/**
* 重载的 private 方法
*/
private void eat(String something, int a) {
System.out.println("在吃" + something);
}
// ================= 公共的 Getters / Setters =================
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
@Override
public String toString() {
return "Student{name = '" + name + "', age = " + age + "}";
}
}

总结 :
Method.invoke是反射从"结构读取"进入"行为执行"的关键步骤。它使得方法调用不再依赖编译期的直接调用语句,而可以由运行期决策产生。
1.8 核心规律总结:反射的通用内省 API
学完了 Constructor(构造方法)、Field(成员变量)、Method(成员方法)的获取与操作后,我们来做一个规律总结。
在这三个核心组件的示例代码中,我们反复用到了 getModifiers() 和 getName()?这是因为无论类里面写的是什么,它们都有一些共通的元数据特征(比如名字和权限修饰符)。
因此,Java 为 Class、Constructor、Field、Method 提供了一套通用的底层 API 来提取这些信息。
1.8.1 通用核心 API
| 方法声明 | 功能核心说明 | 适用对象 |
|---|---|---|
int getModifiers() |
获取该成员的所有修饰符集合(返回一个底层二进制标识整数) | Class, Constructor, Field, Method |
String getName() |
获取该成员的名称(如类名、变量名、方法名) | Class, Constructor, Field, Method |
Class<?> getDeclaringClass() |
获取声明该成员的类(即这个变量或方法是写在哪个类里面的) | Constructor, Field, Method |
1.8.2 深度解析:为什么 getModifiers() 返回的是整数?
当你调用 getModifiers() 时,返回的并不是我们想象中的 "public" 或 "private" 字符串,而是一个整数 (例如 1、2、9 等)。
底层原理:位掩码 (Bitmask)
Java 为了极度节省内存和提高判断效率,使用了二进制的位(0或1) 来表示修饰符。底层的
java.lang.reflect.Modifier类中定义了如下常量:
PUBLIC= 1 (二进制:0000 0001)PRIVATE= 2 (二进制:0000 0010)PROTECTED= 4 (二进制:0000 0100)STATIC= 8 (二进制:0000 1000)FINAL= 16 (二进制:0001 0000)叠加态 :如果一个变量是
public static,它的值就是1 + 8 = 9。这种设计让计算机可以通过极其高效的位运算 (按位与
&)来瞬间判断它是否包含了某个修饰符。
为了把枯燥的整数还原成人类可读的字符串,或者进行快速判断,我们必须配合 Modifier 工具类来使用。
Modifier 工具类常用方法 |
功能核心说明 |
|---|---|
static String toString(int mod) |
将整数翻译为字符串 (例如传入 9,返回 "public static") |
static boolean isPublic(int mod) |
判断该整数中是否包含 public 修饰符 |
static boolean isPrivate(int mod) |
判断该整数中是否包含 private 修饰符 |
static boolean isStatic(int mod) |
判断该整数中是否包含 static 修饰符 |
1.8.3 代码示例

1.8.3.1 TestEntity
java
package reflection.Replenish;
/**
* 测试用的实体类,故意添加多个修饰符用于反射解析测试
*/
public class TestEntity {
// 故意加上多个修饰符进行测试 (public = 1, static = 8, final = 16)
public static final String GREETING = "Hello World";
private int age;
}
1.8.3.2 ModifierReflectDemo
java
package reflection.Replenish;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
/**
* 反射演示类:利用 Modifier 工具类深度解析权限修饰符
*/
public class ModifierReflectDemo {
public static void main(String[] args) throws Exception {
// 1. 获取 Class 对象
Class<?> clazz = Class.forName("reflection.Replenish.TestEntity");
// 2. 获取目标字段:public static final String GREETING
Field field = clazz.getDeclaredField("GREETING");
// ================= 解析修饰符 =================
// 3.1 获取原始整数值
int modifiers = field.getModifiers();
System.out.println("1. 底层返回的整数值: " + modifiers);
// 预期输出: 25 (因为 public=1 + static=8 + final=16)
// 3.2 将整数翻译为直观的字符串 (最常用)
String modifierStr = Modifier.toString(modifiers);
System.out.println("2. 翻译后的文本内容: " + modifierStr);
// 预期输出: public static final
// 3.3 精准判断是否包含某个特定修饰符
boolean isStatic = Modifier.isStatic(modifiers);
boolean isPrivate = Modifier.isPrivate(modifiers);
System.out.println("3. 该字段是否被 static 修饰? " + isStatic); // 预期: true
System.out.println("4. 该字段是否被 private 修饰? " + isPrivate); // 预期: false
// ================= 获取其他通用元数据 =================
System.out.println("5. 该成员的名字是: " + field.getName());
System.out.println("6. 声明该成员的类是: " + field.getDeclaringClass().getName());
}
}

总结 :以后在做反射开发(如编写自动生成文档的工具、代码规范检查器等)时,只要拿到
getModifiers()的返回值,第一时间把它扔给Modifier.toString(modifiers)就可以了。
1.9 反射综合练习1:保存对象信息

1.9.1 场景说明
该练习的核心目标是:对于任意一个对象,都可以把对象内部所有字段名和字段值保存到文件中。
这一场景能够体现反射的通用性:程序不需要提前知道传入对象到底是 Student 还是 Teacher,只要通过 obj.getClass() 获取真实类型,就可以继续读取其字段结构。
1.9.2 代码示例

1.9.2.1 Student
java
package reflection.five;
/**
* 学生实体类,标准 JavaBean
*/
public class Student {
private String name;
private int age;
private char gender;
private double height;
private String hobby;
public Student() {
}
public Student(String name, int age, char gender, double height, String hobby) {
this.name = name;
this.age = age;
this.gender = gender;
this.height = height;
this.hobby = hobby;
}
// --- Getters & Setters ---
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public char getGender() { return gender; }
public void setGender(char gender) { this.gender = gender; }
public double getHeight() { return height; }
public void setHeight(double height) { this.height = height; }
public String getHobby() { return hobby; }
public void setHobby(String hobby) { this.hobby = hobby; }
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age + ", gender=" + gender + ", height=" + height + ", hobby='" + hobby + "'}";
}
}
1.9.2.2 Teacher
java
package reflection.five;
/**
* 老师实体类,标准 JavaBean
*/
public class Teacher {
private String name;
private double salary;
public Teacher() {
}
public Teacher(String name, double salary) {
this.name = name;
this.salary = salary;
}
// --- Getters & Setters ---
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getSalary() { return salary; }
public void setSalary(double salary) { this.salary = salary; }
@Override
public String toString() {
return "Teacher{name='" + name + "', salary=" + salary + "}";
}
}
1.9.2.3 MyReflectDemo
java
package reflection.five;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
/**
* 反射综合练习:
* 对于任意一个对象,都可以把对象所有的字段名和值,保存到文件中去。
*/
public class MyReflectDemo {
public static void main(String[] args) throws IllegalAccessException, IOException {
// 1. 准备测试数据
Student s = new Student("小A", 23, '女', 167.5, "睡觉");
Teacher t = new Teacher("播妞", 10000);
// 2. 调用通用方法,保存对象信息
saveObject(s);
saveObject(t);
System.out.println("数据保存成功,请查看本地文件!");
}
/**
* 把对象里面所有的成员变量名和值保存到本地文件中
*
* @param obj 任意对象
* @throws IllegalAccessException 反射获取私有变量值时可能抛出的异常
* @throws IOException IO 流写出数据时可能抛出的异常
*/
public static void saveObject(Object obj) throws IllegalAccessException, IOException {
// 1. 获取字节码文件的对象
// 因为传入的是具体的对象,所以使用 对象.getClass() 方式最合适
Class<?> clazz = obj.getClass();
// 2. 创建 IO 流 (这里使用追加模式 true,防止第二个对象覆盖第一个对象的数据)
// 注意:如果您的项目下没有 information 文件夹,请手动创建,或者直接写 "information.txt" 保存在项目根目录
BufferedWriter bw = new BufferedWriter(new FileWriter("src\\reflection\\five\\information.txt", true));
// 3. 获取所有的成员变量
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 因为属性都是 private 的,必须暴力反射取消权限校验
field.setAccessible(true);
// 获取成员变量的名字 (如: name, age)
String name = field.getName();
// 获取该成员变量在传入对象 obj 中的具体值 (如: "小A", 23)
Object value = field.get(obj);
// 写出数据
bw.write(name + "=" + value);
bw.newLine();
}
// 4. 添加一个分隔符,方便区分不同对象的数据(优化点)
bw.write("--------------------");
bw.newLine();
// 5. 释放资源
bw.close();
}
}


1.10 反射综合练习2:反射与配置文件结合动态创建

反射最典型的工程价值,是与配置文件结合使用。此时类名和方法名不再写死在源码中,而是由配置文件决定。
1.10.1 执行流程
- 通过
Properties加载配置文件。- 读取目标类名 和目标方法名。
- 通过
Class.forName获取目标类的Class对象。- 通过构造方法创建目标对象。
- 通过方法名获取
Method对象。- 通过
invoke执行目标方法。
1.10.2 代码示例

1.10.2.1 Student
java
package reflection.six;
/**
* 学生实体类
*/
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 学生特有的方法
*/
public void study() {
System.out.println("学生在学习!");
}
// --- Getters & Setters ---
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age + "}";
}
}
1.10.2.2 Teacher
java
package reflection.six;
/**
* 老师实体类
*/
public class Teacher {
private String name;
private double salary;
public Teacher() {
}
public Teacher(String name, double salary) {
this.name = name;
this.salary = salary;
}
/**
* 老师特有的方法
*/
public void teach() {
System.out.println("老师在教书!");
}
// --- Getters & Setters ---
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getSalary() { return salary; }
public void setSalary(double salary) { this.salary = salary; }
@Override
public String toString() {
return "Teacher{name='" + name + "', salary=" + salary + "}";
}
}
1.10.2.3 prop.properties
properties
classname=reflection.six.Student
method=study
配置文件说明 :
配置文件是整个程序的"动态控制中心"。我们彻底抛弃了在 Java 代码中硬编码
new对象的方式,而是将类型信息提取到了这里:
classname:指定程序将要动态加载并实例化的全类名(包名 + 类名)。method:指定对象实例化后,将要动态唤醒执行的方法名。框架的核心魅力 :如果你想让程序从"学生学习"变为执行"老师教书",完全不需要修改、也不需要重新编译任何 Java 源代码 。你只需将上述配置改为
classname=reflection.six.Teacher和method=teach,程序运行的行为就会彻底改变。这就是 Spring 等各大主流框架实现"解耦"和"可插拔"功能的底层基石。
1.10.2.4 MyReflectDemo
java
package reflection.six;
import java.io.FileInputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Properties;
/**
* 反射核心应用演示:【配置文件 + 反射 = 框架的灵魂】
* 目的:在不改变当前类任何代码的情况下,可以创建任意类的对象,并执行任意方法。
*/
public class MyReflectDemo {
public static void main(String[] args) throws Exception {
// ================= 1. 读取配置文件中的信息 =================
Properties prop = new Properties();
// 注意:这里的路径 "src\\prop.properties" 取决于你的项目结构。
// 如果文件直接放在模块根目录下,可能只需要写 "prop.properties"
FileInputStream fis = new FileInputStream("src\\prop.properties");
prop.load(fis);
fis.close();
System.out.println("读取到的配置集合: " + prop);
// ================= 2. 获取全类名和方法名 =================
String className = (String) prop.get("classname");
String methodName = (String) prop.get("method");
System.out.println("目标类名: " + className);
System.out.println("目标方法: " + methodName);
// ================= 3. 利用反射创建对象并运行方法 =================
// 3.1 获取目标类的字节码文件对象
Class<?> clazz = Class.forName(className);
// 3.2 获取无参构造方法,并实例化对象
Constructor<?> con = clazz.getDeclaredConstructor();
Object o = con.newInstance();
System.out.println("反射创建的对象实例: " + o);
// 3.3 获取配置文件中指定的成员方法
Method method = clazz.getDeclaredMethod(methodName);
// 习惯性动作:防止方法是 private 的,临时取消权限校验
method.setAccessible(true);
// 3.4 运行该方法
System.out.print("执行方法的结果: ");
method.invoke(o);
}
}

总结:配置文件负责描述"要创建谁、要调用谁",反射负责完成"创建与调用"。二者结合以后,程序具备了不修改源码即可替换实现的能力。
2. 动态代理

2.1 为什么需要代理
动态代理用于在不修改目标类源码的前提下 ,对目标对象的方法进行增强或拦截。
代理调用链可以抽象为:

也就是说,外部调用者并不直接接触真实对象,而是先经过代理对象。代理对象在转发调用之前,可以执行准备工作、权限校验、日志记录、性能统计等增强逻辑。
2.2 动态代理三要素
| 要素 | 说明 |
|---|---|
| 真正干活的对象 | 业务逻辑的真实执行者 |
| 代理对象 | 对外暴露的访问入口 |
| 通过代理调用方法 | 调用会先进入代理逻辑,再决定是否转发给真实对象 |
注意 :JDK 动态代理要求代理对象 和被代理对象 基于同一个接口。接口中定义的方法,就是代理能够增强或拦截的方法范围。
2.3 创建代理对象的核心 API
如何为 Java 对象创建一个代理对象?java.lang.reflect.Proxy 类提供了为对象产生代理对象的核心方法:
| 方法声明 |
|---|
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) |
核心参数解析:
- 参数一 (
loader) :用于指定用哪个类加载器,去加载生成的代理类。 - 参数二 (
interfaces) :指定接口。这些接口用于指定生成的代理长什么样,也就是它拥有哪些方法。 - 参数三 (
h) :调用处理程序。用来指定生成的代理对象要干什么事情(即具体的拦截和增强逻辑)。
2.4 代码示例


2.4.1 Star
java
package reflection.seven;
/**
* 明星接口
* 我们需要把所有想要被代理的方法,统一定义在接口当中。
*/
public interface Star {
/**
* 唱歌方法
*
* @param name 歌曲名称
* @return 感谢语
*/
public abstract String sing(String name);
/**
* 跳舞方法
*/
public abstract void dance();
}
2.4.2 BigStar
java
package reflection.seven;
/**
* 大明星类(目标类/被代理类)
* 必须实现对应的接口。
*/
public class BigStar implements Star {
private String name;
public BigStar() {
}
public BigStar(String name) {
this.name = name;
}
// ================= 核心业务方法 =================
@Override
public String sing(String name) {
System.out.println(this.name + " 正在唱 " + name);
return "谢谢大家";
}
@Override
public void dance() {
System.out.println(this.name + " 正在跳舞");
}
// ================= Getters & Setters =================
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2.4.3 ProxyUtil
java
package reflection.seven;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 代理工具类
* 用于给指定的明星对象创建一个代理对象(即"经纪人")。
*/
public class ProxyUtil {
/**
* 给一个明星对象,创建一个代理对象
*
* @param bigStar 被代理的明星对象(被代理对象)
* @return 给明星创建的代理对象(实现了 Star 接口)
*/
public static Star createProxy(BigStar bigStar) {
/*
* java.lang.reflect.Proxy 类提供了为对象产生代理对象的方法:
* public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
* * 参数一 (loader): 类加载器。用于加载动态生成的代理类。通常使用当前类的加载器即可。
* * 参数二 (interfaces): 接口数组。指定生成的代理对象长什么样,也就是它需要具备哪些方法。
* * 参数三 (h): 调用处理程序 (InvocationHandler)。用来指定代理对象在被调用方法时,具体要干什么事情。
*/
Star starProxy = (Star) Proxy.newProxyInstance(
ProxyUtil.class.getClassLoader(), // 参数一:类加载器
new Class<?>[]{Star.class}, // 参数二:指定的接口 (注意:这里补全了泛型 <?> 消除警告)
new InvocationHandler() { // 参数三:具体要干的事情
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*
* invoke 方法的作用:
* 当外面调用代理对象的任何方法时,都会自动走到这个 invoke 方法里面来!
* * 参数解析:
* @param proxy 代理对象本身(通常在内部用不到)
* @param method 正在被调用的方法对象(比如 sing 或 dance)
* @param args 调用方法时传递的实际参数数组
*/
// 1. 代理对象进行前置拦截(比如:收钱、准备场地)
if ("sing".equals(method.getName())) {
System.out.println("【代理人拦截】准备话筒,收钱!");
} else if ("dance".equals(method.getName())) {
System.out.println("【代理人拦截】准备场地,收钱!");
}
// 2. 核心逻辑:去找真正的大明星开始唱歌或者跳舞
// 代码表现形式:利用反射,调用大明星(bigStar)里对应的方法
Object result = method.invoke(bigStar, args);
// 3. 将大明星方法的返回值,原封不动地返回给外部调用者
return result;
}
}
);
return starProxy;
}
}
2.4.4 Test
java
package reflection.seven;
/**
* 测试类
* 需求:外面的人想要请大明星唱一首歌或者跳一段舞。
*/
public class Test {
public static void main(String[] args) {
// 1. 创建被代理的对象(大明星本人)
BigStar bigStar = new BigStar("鸡哥");
// 2. 获取该对象的代理对象(找经纪人)
// 注意:代理对象实现了和目标对象相同的接口,所以可以用 Star 接口来接收
Star proxy = ProxyUtil.createProxy(bigStar);
// 3. 调用代理对象的唱歌方法
// 过程:外部调用 sing -> 触发 InvocationHandler 的 invoke -> 打印"准备话筒收钱" -> 反射执行真正的 sing -> 返回"谢谢"
System.out.println("--- 测试唱歌 ---");
String result = proxy.sing("只因你太美");
System.out.println("方法返回值: " + result);
// 4. 调用代理对象的跳舞方法
System.out.println("\n--- 测试跳舞 ---");
proxy.dance();
}
}

2.5 核心逻辑深度解析
动态代理最核心的执行点是 InvocationHandler 中的 invoke 方法。当代理对象被创建后,外部对它的每一次方法调用,都会被强制转发到该 invoke 方法中。
代理与反射的结合:
代理对象内部高度依赖反射。代理对象接收到的并不是一个普通的方法调用语句,而是一个元数据
Method对象。
Method method:代表当前外部正在调用的方法。Object[] args:代表调用该方法时传入的实际参数。method.invoke(bigStar, args):代表代理对象自己干完活(拦截增强)后,利用反射把请求"放行"给真正的大明星。
2.6 反射与动态代理的关系
动态代理可以看作是反射在"方法调用增强"领域的一种高级应用模式。
核心关系梳理:
- 没有反射就没有动态代理 :如果不利用反射(
Method.invoke),代理对象根本无法在不知道目标类型的情况下动态转发方法。- 职责分工明确:反射赋予了程序在运行期解析和执行的能力;而动态代理利用这个能力,构建了一道防火墙/增强环,把非核心业务逻辑(如日志、事务、权限)从核心业务代码中剥离了出来。
2.7 模块小结
| 知识点 | 核心结论 |
|---|---|
Class |
反射体系的入口,表示运行期类型信息 |
Constructor |
用于动态创建对象 |
Field |
用于动态读取和修改成员变量 |
Method |
用于动态调用成员方法 |
| 配置文件结合反射 | 使程序不修改源码也能替换目标类和方法 |
| 动态代理 | 在不修改目标类的前提下增强或拦截方法 |
| JDK 动态代理限制 | 必须基于接口生成代理对象 |
最终结论:反射解决的是"运行期如何识别并操作类结构"的问题,动态代理解决的是"运行期如何增强对象行为"的问题。二者共同构成了 Java 后端框架(如 Spring AOP)中配置驱动、自动装配、方法增强等机制的最底层技术基础。