当前位置: 首页 > news >正文

Java-反射机制

在 Java 编程中,“反射” 是一个贯穿基础与进阶的核心概念,它允许程序在运行时动态获取类的结构、调用方法、操作属性,甚至创建对象 —— 无需在编译期明确知道类的具体信息。

一、反射是什么?

首先明确一个关键定义:Java 反射(Reflection)是 Java 语言提供的一种能力,允许程序在运行时(而非编译时)访问、检测和修改类、对象、方法、属性等程序元素的信息

反射的核心价值

  • 动态性:打破编译期的固定依赖,运行时灵活操作类结构(比如根据配置文件加载不同类)。
  • 通用性:可编写通用框架(如 Spring、MyBatis),通过反射适配任意类,无需为每个类单独写代码。
  • 穿透性:支持 “暴力反射”,可突破类成员的访问权限限制(如操作 private 私有属性 / 方法)。

二、反射的前提:获取 “类对象”

反射的所有操作,都必须基于一个核心载体 ——Class 类的对象(简称 “类对象”)。每个类在 JVM 中只会被加载一次,因此同一个类的类对象全局唯一。

获取类对象的 3 种核心方式

这 3 种方式对应类的 “生命周期”(硬盘→内存→对象)整理如下:

获取方式语法示例适用场景关键说明
1. Class.forName (全类名)Class pClass = Class.forName("reflect.Person");编译期未知类名,需动态指定(如读配置文件)需传入 “包名 + 类名”,会触发类的加载,可能抛出 ClassNotFoundException
2. 类名.classClass 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()获取简单类名(仅类名)

pClass.getSimpleName() → "Person"

5.2 反射的注意事项

反射虽强大,但存在以下问题,使用时需谨慎:

  1. 性能开销:反射操作需在运行时解析类结构,比直接调用(如 person.getAge())慢,高频场景(如循环中)需避免。
  2. 安全风险:暴力反射突破了访问权限限制,可能破坏类的封装性(如修改私有变量),需确保操作的合理性。
  3. 代码可读性:反射代码较抽象,不如直接调用直观,需添加清晰注释。
  4. 兼容性风险:若类结构修改(如方法名、参数列表变更),反射代码可能抛出 NoSuchMethodException 等异常,需做好异常处理。

六、总结:反射知识体系图谱

最后,用一张图谱梳理本文核心内容,方便复习回顾:

Java 反射
├─ 核心前提:获取类对象(3种方式)
│  ├─ Class.forName(全类名) → 动态加载
│  ├─ 类名.class → 静态获取
│  └─ 对象.getClass() → 实例反推
├─ 核心操作(3大组件)
│  ├─ 成员变量(Field):getDeclaredFields()/getFields() + set()/get() + 暴力反射
│  ├─ 成员方法(Method):getDeclaredMethods()/getMethods() + invoke() + 暴力反射
│  └─ 构造方法(Constructor):getDeclaredConstructors()/getConstructors() + newInstance()
├─ 实战场景:结合配置文件动态加载
└─ 注意事项:性能、安全、可读性、兼容性
http://www.dtcms.com/a/354936.html

相关文章:

  • Java 多线程环境下的全局变量缓存实践指南
  • PyTorch 张量核心知识点
  • 【物联网】什么是 Arduino Nano 33 IoT?
  • 基于springboot的二手车交易系统
  • WEEX唯客上线C2C交易平台:打造安全便捷的用户交易体验
  • FISCO-BCOS-Python 模板
  • 上海控安:GB 44495-2024《汽车整车信息安全技术要求》标准解读和测试方案
  • 动手学深度学习(pytorch版):第七章节—现代卷积神经网络(6)残差网络(ResNet)
  • Ubuntu 使用百度云的bypy上传和下载数据
  • ArcGIS+Fragstats:土地利用统计分析、景观格局指数计算与地图制图
  • 终极实战 - 全链路排查一次“502 Bad Gateway”
  • Linux并发与竞争
  • 达梦数据库-重做日志文件(三)-自动化迁移脚本和检查 磁盘 I/O 性能建议
  • 详细介绍Linux 内存管理 匿名页面和page cache页面有什么区别?
  • Mybatis 与 Springboot 集成过程详解
  • vue有哪些优缺点
  • 前端实现Linux查询平台:打造高效运维工作流
  • 从图卷积网络(GCN)到简化图卷积网络(SGC)的对话
  • RAG系统深度优化全攻略:从理论到实践的高性能实现
  • 【C语言16天强化训练】从基础入门到进阶:Day 14
  • NVFP4量化技术深度解析:4位精度下实现2.3倍推理加速
  • 内网对抗-红日靶场4通关详解
  • 财务数据报销画像技术实现:从数据采集到智能决策的全流程解析
  • 2025docker快速部署Nginx UI可视化管理平台
  • Unity3d使用SerialPortUtilityPro读取串口数据
  • Linux(一) | 初识Linux与目录管理基础命令掌握
  • Libvio 访问异常排查指南
  • 2021/07 JLPT听力原文 问题一 2番
  • 【python】@staticmethod装饰器
  • nginx 配置文件初识全局块、events、http、server、location 的层级关系