在 Java 编程中,“反射” 是一个贯穿基础与进阶的核心概念,它允许程序在运行时动态获取类的结构、调用方法、操作属性,甚至创建对象 —— 无需在编译期明确知道类的具体信息。
一、反射是什么?
首先明确一个关键定义:Java 反射(Reflection)是 Java 语言提供的一种能力,允许程序在运行时(而非编译时)访问、检测和修改类、对象、方法、属性等程序元素的信息。
反射的核心价值
- 动态性:打破编译期的固定依赖,运行时灵活操作类结构(比如根据配置文件加载不同类)。
- 通用性:可编写通用框架(如 Spring、MyBatis),通过反射适配任意类,无需为每个类单独写代码。
- 穿透性:支持 “暴力反射”,可突破类成员的访问权限限制(如操作 private 私有属性 / 方法)。
二、反射的前提:获取 “类对象”
反射的所有操作,都必须基于一个核心载体 ——Class 类的对象(简称 “类对象”)。每个类在 JVM 中只会被加载一次,因此同一个类的类对象全局唯一。
获取类对象的 3 种核心方式
这 3 种方式对应类的 “生命周期”(硬盘→内存→对象)整理如下:
获取方式 | 语法示例 | 适用场景 | 关键说明 |
---|---|---|---|
1. Class.forName (全类名) | Class pClass = Class.forName("reflect.Person"); | 编译期未知类名,需动态指定(如读配置文件) | 需传入 “包名 + 类名”,会触发类的加载,可能抛出 ClassNotFoundException |
2. 类名.class | Class pClass = Person.class; | 编译期已知类名,需静态获取 | 不会触发类的加载,仅获取类的静态结构信息 |
3. 对象.getClass () | Person person = new Person(); Class pClass = person.getClass(); | 已有对象实例,需通过对象反推类信息 | 依赖具体对象,适用于 “已知对象但未知类” 的场景 |
验证唯一性:同一个类的 3 种方式获取的类对象地址完全相同
Class class1 = Class.forName("reflect.Person");
Class class2 = Person.class;
Person person = new Person();
Class class3 = person.getClass();// 输出结果均为 true,证明类对象唯一
System.out.println(class1 == class2);
System.out.println(class2 == class3);
三、反射核心操作:全方位操控类结构
获取类对象后,即可通过反射 API 操作类的三大核心组件:成员变量、成员方法、构造方法。以下结合 Test.java 和 Person.java 的实践代码,分模块梳理。
模块 1:操作 “成员变量”(Field)
成员变量的反射操作,核心是 “获取变量” 和 “读写变量值”,需区分 “所有权限” 和 “仅公共权限(public)”,同时支持暴力反射突破私有限制。
1.1 获取成员变量的 4 个核心方法
方法名 | 作用 | 访问权限范围 | 是否包含父类变量 |
---|---|---|---|
getDeclaredFields() | 获取当前类的所有成员变量 | 任意权限(public/private/protected) | 不包含 |
getDeclaredField(String name) | 获取当前类的指定名称成员变量 | 任意权限 | 不包含 |
getFields() | 获取当前类的所有公共成员变量 | 仅 public | 包含父类的 public 变量 |
1.2 读写变量值:set () 与 get ()
- 语法:
- 写值:
field.set(对象实例, 变量值)
(为指定对象的该变量赋值) - 读值:
field.get(对象实例)
(获取指定对象的该变量值)
- 写值:
- 关键注意:若变量是
private
私有权限,直接读写会抛出IllegalAccessException
,需先调用field.setAccessible(true)
开启 “暴力反射”,强制跳过权限检查。
1.3 实战代码
// 1. 创建 Person 对象实例
Person person = new Person();
// 2. 获取类对象
Class pClass = Class.forName("reflect.Person");// 3. 获取所有成员变量(含 private)
Field[] allFields = pClass.getDeclaredFields();
for (Field field : allFields) {System.out.println(field); // 输出:private java.lang.String reflect.Person.name、private int reflect.Person.age、public java.lang.String reflect.Person.from
}// 4. 获取指定私有变量 name(需暴力反射)
Field nameField = pClass.getDeclaredField("name");
nameField.setAccessible(true); // 开启暴力反射,突破 private 限制
nameField.set(person, "赵嘉成"); // 赋值
System.out.println(nameField.get(person)); // 取值,输出:赵嘉成// 5. 获取指定公共变量 from(无需暴力反射)
Field fromField = pClass.getDeclaredField("from");
fromField.set(person, "中国"); // 直接赋值
System.out.println(person); // 输出:Person [name=赵嘉成, age=0, from=中国]
模块 2:操作 “成员方法”(Method)
成员方法的反射操作,核心是 “获取方法” 和 “执行方法”,同样需区分权限范围,支持暴力反射调用私有方法。
2.1 获取成员方法的 4 个核心方法
方法名 | 作用 | 访问权限范围 | 是否包含父类方法 |
---|---|---|---|
getDeclaredMethods() | 获取当前类的所有成员方法 | 任意权限 | 不包含 |
getDeclaredMethod(String name, Class<?>... paramTypes) | 获取当前类的指定名称和参数列表的方法 | 任意权限 | 不包含 |
getMethods() | 获取当前类的所有公共成员方法 | 仅 public | 包含父类的 public 方法(如 Object 的 toString ()) |
getMethod(String name, Class<?>... paramTypes) | 获取当前类的指定名称和参数列表的公共方法 | 仅 public | 包含父类的 public 方法 |
2.2 执行方法:invoke ()
语法:method.invoke(对象实例, 方法参数值...)
- 若方法是静态方法(static),对象实例可传
null
; - 若方法无参数,参数值部分可省略或传空数组;
- 若方法有返回值,
invoke()
会返回该值(需强转)。
权限注意:私有方法需先调用 method.setAccessible(true)
开启暴力反射。
2.3 实战代码
// 1. 获取类对象(省略,同上)
Class pClass = Class.forName("reflect.Person");
Person person = new Person();// 2. 获取所有方法(含 private 的 run())
Method[] allMethods = pClass.getDeclaredMethods();
for (Method method : allMethods) {System.out.println(method); // 输出:getAge()、setAge(int)、run() 等
}// 3. 获取指定私有方法 run()(无参数)
Method runMethod = pClass.getDeclaredMethod("run");
runMethod.setAccessible(true); // 暴力反射突破 private
runMethod.invoke(person); // 执行 run() 方法(无返回值)// 4. 获取指定公共方法 getAge()(无参数,有返回值)
Method getAgeMethod = pClass.getMethod("getAge");
int age = (int) getAgeMethod.invoke(person); // 执行并接收返回值
System.out.println(age); // 输出:0(Person 初始 age 为 0)
模块 3:操作 “构造方法”(Constructor)
构造方法的反射操作,核心是 “获取构造器” 和 “创建对象”(替代 new
关键字),支持通过无参 / 有参构造器创建实例。
3.1 获取构造方法的 4 个核心方法
方法名 | 作用 | 访问权限范围 | 是否包含父类构造器 |
---|---|---|---|
getDeclaredConstructors() | 获取当前类的所有构造方法 | 任意权限 | 不包含(构造器不能继承) |
getDeclaredConstructor(Class<?>... paramTypes) | 获取当前类的指定参数列表的构造方法 | 任意权限 | 不包含 |
getConstructors() | 获取当前类的所有公共构造方法 | 仅 public | 不包含 |
getConstructor(Class<?>... paramTypes) | 获取当前类的指定参数列表的公共构造方法 | 仅 public | 不包含 |
3.2 创建对象:newInstance ()
- 语法:
constructor.newInstance(构造参数值...)
- 无参构造器:参数值部分可省略,直接
constructor.newInstance()
; - 有参构造器:需传入与参数列表匹配的参数值;
- 私有构造器:需先调用
constructor.setAccessible(true)
开启暴力反射。
- 无参构造器:参数值部分可省略,直接
3.3 实战代码
// 1. 获取类对象(省略)
Class pClass = Class.forName("reflect.Person");// 2. 获取所有构造器(Person 有 3 个:无参、String name、String name+int age)
Constructor[] allConstructors = pClass.getDeclaredConstructors();
for (Constructor constructor : allConstructors) {System.out.println(constructor); // 输出:Person()、Person(java.lang.String)、Person(java.lang.String,int)
}// 3. 获取无参公共构造器,创建对象
Constructor noArgConstructor = pClass.getConstructor();
Person person1 = (Person) noArgConstructor.newInstance();
System.out.println(person1); // 输出:Person [name=null, age=0, from=null]// 4. 获取有参公共构造器(String name + int age),创建对象
Constructor twoArgConstructor = pClass.getConstructor(String.class, int.class);
Person person2 = (Person) twoArgConstructor.newInstance("丽丽", 20);
System.out.println(person2); // 输出:Person [name=丽丽, age=20, from=null]
四、反射实战:结合配置文件实现 “动态加载”
反射的核心优势是 “动态性”,而结合配置文件(如 .properties)可实现 “不修改代码,仅改配置就能切换类 / 方法”。Test.java 中已实现该场景,整理如下:
需求场景
通过 refconfig.properties
配置文件指定类名和方法名,运行时动态加载类并调用方法,无需硬编码类名。
步骤拆解与代码
创建配置文件(refconfig.properties):
# 配置要加载的类(全类名)
reflect.className=reflect.Person
# 配置要调用的方法名
reflect.methodName=getAge
读取配置文件并动态反射:
// 1. 创建 Properties 对象,用于读取配置文件
Properties props = new Properties();// 2. 通过类加载器加载配置文件(注意路径:src/main/resources/reflect/refconfig.properties)
InputStream in = Person.class.getClassLoader().getResourceAsStream("reflect/refconfig.properties");
props.load(in); // 读取配置内容// 3. 从配置中获取类名和方法名
String className = props.getProperty("reflect.className");
String methodName = props.getProperty("reflect.methodName");// 4. 动态加载类,获取类对象
Class dynamicClass = Class.forName(className);
System.out.println(dynamicClass); // 输出:class reflect.Person// 5. 动态获取方法并执行(这里以无参方法 getAge() 为例)
Method dynamicMethod = dynamicClass.getMethod(methodName);
Object instance = dynamicClass.getConstructor().newInstance(); // 创建对象
dynamicMethod.invoke(instance); // 执行方法,输出:getAge方法执行
核心价值
若后续需要切换为操作 Cat.java
(而非 Person.java
),只需修改配置文件的 reflect.className=reflect.Cat
,无需修改 Java 代码 —— 这正是框架(如 Spring)“解耦” 的核心原理。
五、反射关键补充:类信息获取与注意事项
5.1 获取类的基本信息
通过类对象可快速获取类的名称、包名等信息,Test.java 中已实践:
方法名 | 作用 | 示例(Person 类) |
---|---|---|
getName() | 获取全类名(包名 + 类名) | pClass.getName() → "reflect.Person" |
getSimpleName() | 获取简单类名(仅类名) |
|
5.2 反射的注意事项
反射虽强大,但存在以下问题,使用时需谨慎:
- 性能开销:反射操作需在运行时解析类结构,比直接调用(如
person.getAge()
)慢,高频场景(如循环中)需避免。 - 安全风险:暴力反射突破了访问权限限制,可能破坏类的封装性(如修改私有变量),需确保操作的合理性。
- 代码可读性:反射代码较抽象,不如直接调用直观,需添加清晰注释。
- 兼容性风险:若类结构修改(如方法名、参数列表变更),反射代码可能抛出
NoSuchMethodException
等异常,需做好异常处理。
六、总结:反射知识体系图谱
最后,用一张图谱梳理本文核心内容,方便复习回顾:
Java 反射
├─ 核心前提:获取类对象(3种方式)
│ ├─ Class.forName(全类名) → 动态加载
│ ├─ 类名.class → 静态获取
│ └─ 对象.getClass() → 实例反推
├─ 核心操作(3大组件)
│ ├─ 成员变量(Field):getDeclaredFields()/getFields() + set()/get() + 暴力反射
│ ├─ 成员方法(Method):getDeclaredMethods()/getMethods() + invoke() + 暴力反射
│ └─ 构造方法(Constructor):getDeclaredConstructors()/getConstructors() + newInstance()
├─ 实战场景:结合配置文件动态加载
└─ 注意事项:性能、安全、可读性、兼容性