深入理解Java反射(超详细)

🌈个人主页 :一条泥憨鱼 (欢迎各位大佬莅临)

🎬精选专栏:数据结构与算法JavaSE,苍穹外卖日记AI学习

​​​​​什么是反射?通俗理解

我们先想一个生活中的例子:你手里有一个神秘的黑盒子,不知道里面装了什么。正常情况下,你要先看说明书(源码),才知道它有哪些按钮、怎么用。

但如果你有一台"X光机",不用看说明书,直接对着黑盒子扫一下,就能看清楚里面有哪些零件、每个零件叫什么名字、怎么操作------这台"X光机"就是 Java 的反射机制

// 官方定义Java 反射机制是指在程序运行时(Runtime),对于任意一个类,都能知道这个类的所有属性和方法;对于任意一个对象,都能调用它的任意方法和属性。这种动态获取信息以及动态调用对象方法的功能,称为 Java 的反射机制。

关键词是"运行时"。普通代码在编译时就确定了用哪个类、调哪个方法。而反射可以在程序跑起来之后,才决定要操作哪个类------这带来了极大的灵活性,是 Spring、MyBatis 等主流框架的底层核心。

记忆技巧

正常写代码:编译期 就确定类和方法 → 安全、快速

使用反射:运行期才确定类和方法 → 灵活、强大


获取 Class 对象的三种方式

使用反射的第一步,是拿到目标类的 Class 对象 。你可以把 Class 对象理解为那台"X光机"扫描后得到的"类的说明书",里面记录了该类的一切信息。

Java 提供了三种方式来获取 Class 对象:

方式一:类名.class(最简单直接)

已知类名时使用,编译期就确定,不会抛出异常。

java 复制代码
Class<String> clazz = String.class;
System.out.println(clazz); // 输出: class java.lang.String

方式二:对象.getClass()(已有对象时使用)

当你手里已经有一个对象实例,可以通过它获取对应的 Class

java 复制代码
String str = "Hello, 反射!";
Class<?> clazz = str.getClass();
System.out.println(clazz); // 输出: class java.lang.String

方式三:Class.forName("全类名")(最灵活,框架常用)

通过完整类名字符串 动态加载类。类名可以来自配置文件、数据库等,是框架最常用的方式。注意:需要处理**ClassNotFoundException**。

Java

java 复制代码
try {
    // 全类名 = 包名 + 类名
    Class<?> clazz = Class.forName("java.lang.String");
    System.out.println(clazz); // 输出: class java.lang.String
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

重要提示

同一个类,无论用哪种方式获取 Class 对象,得到的都是同一个 对象(JVM 中每个类只有一份 Class 对象)。

三种方式的结果完全相同:String.class == str.getClass() == Class.forName("java.lang.String")


利用反射获取构造方法

拿到 Class 对象后,我们就可以"解剖"这个类了。先来看如何获取构造方法(Constructor)

首先,我们定义一个示例类 Student,后续所有例子都基于它:

Java · Student.java(示例类)

java 复制代码
public class Student {
    // 成员变量
    public 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(name + " 正在学习Java!");
    }

    private void sleep() {
        System.out.println(name + " 在睡觉...");
    }

    // getter/setter 省略...
}

常用的获取构造方法 API:

方法 说明
getConstructors() 获取所有 public 构造方法
getDeclaredConstructors() 获取所有构造方法(包括私有)
getConstructor(参数类型...) 获取指定的 public 构造方法
getDeclaredConstructor(参数类型...) 获取指定的任意构造方法

获取并使用构造方法

java 复制代码
import java.lang.reflect.Constructor;

Class<?> clazz = Class.forName("Student");

// 1. 获取所有 public 构造方法
Constructor[] constructors = clazz.getConstructors();
for (Constructor c : constructors) {
    System.out.println(c);
}

// 2. 获取有参构造并创建对象
Constructor<?> con = clazz.getConstructor(String.class, int.class);
Object stu = con.newInstance("张三", 18);  // 等价于 new Student("张三", 18)
System.out.println(stu);

// 3. 使用无参构造创建对象(简便写法)
Object stu2 = clazz.getDeclaredConstructor().newInstance();
System.out.println(stu2);

小贴士

con.newInstance(参数...) 就是反射版的 new 关键字。参数类型和顺序必须与构造方法一致。


利用反射获取成员变量

反射不仅能创建对象,还能直接读写对象的成员变量(Field),即使是 private 修饰的私有变量也能访问!

方法 说明
getFields() 获取所有 public 成员变量
getDeclaredFields() 获取所有成员变量(包括私有)
getField("变量名") 获取指定 public 变量
getDeclaredField("变量名") 获取指定任意变量

获取并操作成员变量

java 复制代码
import java.lang.reflect.Field;

Class<?> clazz = Student.class;

// 先创建一个 Student 对象用于测试
Object stu = clazz.getDeclaredConstructor(String.class, int.class)
                    .newInstance("张三", 18);

// 1. 获取 public 变量 name 并修改
Field nameField = clazz.getField("name");
nameField.set(stu, "李四");              // 修改 name 值
System.out.println(nameField.get(stu)); // 读取 name 值 → 李四

// 2. 获取 private 变量 age
//    ⚠️ 访问私有变量必须先调用 setAccessible(true) 暴力破解!
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true);          // 取消私有访问限制(关键!)
ageField.set(stu, 20);                  // 修改 age 值
System.out.println(ageField.get(stu)); // 读取 age 值 → 20

⚠️ 重点:setAccessible(true)访问 private 成员时,必须先调用 setAccessible(true) ,否则会抛出 IllegalAccessException。这个操作相当于"强制解锁私有权限",在框架中非常常见,但在业务代码中要谨慎使用。


利用反射获取成员方法

反射还可以获取并调用任意方法(Method),包括私有方法,甚至可以在不知道方法名的情况下,通过字符串来动态调用。

方法 说明
getMethods() 获取所有 public 方法(含父类继承的)
getDeclaredMethods() 获取本类所有方法(不含继承,含私有)
getMethod("方法名", 参数类型...) 获取指定 public 方法
getDeclaredMethod("方法名", 参数类型...) 获取本类指定任意方法

获取并调用成员方法

java 复制代码
import java.lang.reflect.Method;

Class<?> clazz = Student.class;
Object stu = clazz.getDeclaredConstructor(String.class, int.class)
                    .newInstance("张三", 18);

// 1. 调用无参 public 方法 study()
Method studyMethod = clazz.getMethod("study");
studyMethod.invoke(stu); // 输出:张三 正在学习Java!

// 2. 调用带参方法(假设有 setName(String name) 方法)
Method setNameMethod = clazz.getMethod("setName", String.class);
setNameMethod.invoke(stu, "王五"); // 等价于 stu.setName("王五")

// 3. 调用 private 方法 sleep()
Method sleepMethod = clazz.getDeclaredMethod("sleep");
sleepMethod.setAccessible(true);  // 私有方法同样需要暴力破解
sleepMethod.invoke(stu);           // 输出:王五 在睡觉...

// 4. 获取方法的返回值
Method getAgeMethod = clazz.getMethod("getAge");
Object result = getAgeMethod.invoke(stu);
System.out.println("年龄: " + result); // 年龄: 18

method.invoke()

参数说明 method.invoke(obj, arg1, arg2, ...)

第一个参数:要调用方法的对象实例

后续参数:方法所需的实际参数值

返回值:方法的返回值(如果是 void,则返回 null)


反射的实际作用与应用场景

你可能会问:"我直接 new 一个对象不就好了,为什么要用这么麻烦的反射?"

反射的真正价值,在于**"当你不知道要用哪个类的时候"**。来看一个简单的框架模拟案例:

案例:模拟框架------根据配置文件动态创建对象

假设我们有一个配置文件 config.properties

java 复制代码
# 只需修改这里,程序行为就会改变
className=com.example.Student
methodName=study

使用反射读取配置并动态执行

java 复制代码
import java.util.Properties;
import java.io.InputStream;
import java.lang.reflect.*;

public class ReflectDemo {
    public static void main(String[] args) throws Exception {

        // 1. 读取配置文件
        Properties prop = new Properties();
        InputStream is = ReflectDemo.class.getClassLoader()
                              .getResourceAsStream("config.properties");
        prop.load(is);

        // 2. 获取类名和方法名
        String className  = prop.getProperty("className");
        String methodName = prop.getProperty("methodName");

        // 3. 使用反射动态创建对象并调用方法
        //    ✅ 不修改 Java 代码,只改配置文件就能切换行为!
        Class<?> clazz = Class.forName(className);
        Object obj = clazz.getDeclaredConstructor().newInstance();
        Method method = clazz.getMethod(methodName);
        method.invoke(obj);
    }
}

现在把配置文件里的 className 改成 com.example.Teacher,程序就会操作 Teacher 类,完全不需要修改 Java 代码重新编译。这正是 Spring 框架依赖注入(IoC)的基本原理!

反射在主流框架中的应用

Spring IoC :读取 XML / 注解配置 → 用反射创建 Bean 对象并注入依赖
MyBatis :读取 SQL 结果集 → 用反射把列值赋给 Java 对象的字段
Jackson / Gson :JSON 反序列化 → 用反射把 JSON 字段映射到 Java 对象
JUnit:扫描带 @Test 注解的方法 → 用反射自动执行测试方法


反射的优缺点总结

✅ 优点

**灵活性强:**运行时动态操作类,无需在编译期确定类型,是框架设计的基石。

✅ 优点

**降低耦合:**通过配置文件驱动,代码与具体类解耦,修改配置即可切换实现。

⚠️ 缺点

**性能开销:**反射涉及动态解析,比直接调用慢,频繁调用场景应缓存 Method / Field 对象。

⚠️ 缺点

安全风险: setAccessible(true) 可绕过访问控制,破坏封装性,需谨慎使用。

一张图回顾全部知识点

复制代码
Java 反射
├── 获取 Class 对象
│   ├── 类名.class             ← 编译期,最安全
│   ├── 对象.getClass()        ← 有对象实例时用
│   └── Class.forName("全类名") ← 运行时,最灵活(框架常用)
│
├── 获取构造方法 (Constructor)
│   ├── getConstructors()      ← 所有 public 构造
│   ├── getDeclaredConstructors() ← 所有构造(含 private)
│   └── con.newInstance(参数)  ← 创建对象
│
├── 获取成员变量 (Field)
│   ├── getFields()            ← 所有 public 字段
│   ├── getDeclaredFields()    ← 所有字段(含 private)
│   ├── field.get(obj)         ← 读取值
│   ├── field.set(obj, val)    ← 修改值
│   └── setAccessible(true)   ← 访问 private 必须调用!
│
├── 获取成员方法 (Method)
│   ├── getMethods()           ← 所有 public 方法(含继承)
│   ├── getDeclaredMethods()   ← 本类所有方法(含 private)
│   ├── method.invoke(obj, args...) ← 调用方法
│   └── setAccessible(true)   ← 调用 private 必须调用!
│
└── 应用场景
    ├── Spring IoC 依赖注入
    ├── MyBatis ORM 映射
    ├── JSON 序列化 / 反序列化
    └── 单元测试框架(JUnit)

反射是"高阶武器",初学阶段不需要在业务代码中大量使用。但理解反射是读懂 Spring、MyBatis 等框架源码的前提。掌握本文的内容,你已经具备了探索主流框架底层原理的基础能力!

今天的学习就暂时告一段落啦,如果文章对您有用的话,还请留下一个免费的小心心和关注哦!

祝您工作顺利,生活愉快。我们下期再见!

相关推荐
sycmancia1 小时前
Qt——Qt中的调色板
开发语言·qt
J-query1 小时前
修改AndroidStudio的Boot Java Runtime for the IDE后,AndroidStudio启动就报错
java·开发语言·ide·android studio
雪度娃娃1 小时前
ASIO异步通信——服务器网络层和逻辑层设计
开发语言·网络·c++·php
Han.miracle1 小时前
Java HashMap 与 ConcurrentHashMap 核心原理总结:从 Hash 冲突到 LongAdder
java·算法·哈希算法
Zhang~Ling1 小时前
C++ 多态完全指南:虚函数、重写、虚表与动态绑定深度解析
开发语言·c++
不负岁月无痕1 小时前
STL-- C++ list类 模拟实现
开发语言·c++·list
Gauss松鼠会1 小时前
GaussDB(DWS) SQL性能问题案例集
java·数据库·经验分享·spring boot·后端·sql·gaussdb
JSON_L1 小时前
PHP 高精度计算完全指南:彻底解决浮点数精度丢失
开发语言·php
江屿风1 小时前
C++OJ题经验总结(竞赛)3
开发语言·c++·笔记·算法