Java 反射机制深度解析:从对象创建到私有成员操作

前言:什么是反射?为什么需要它?

你是否好奇过:Spring 框架如何根据配置文件自动创建对象?ORM 框架如何直接操作类的私有属性?这些 "黑科技" 的底层核心,正是 Java 的反射机制

简单来说,反射就是程序在运行时可以 "看透" 类的内部结构(包括属性、方法、构造器等),并动态操作这些结构的能力。就像给程序装上了 "透视眼",即使编译时不知道类的具体信息,运行时也能灵活操控。

一、反射的基石:Class 对象

要使用反射,必须先获取目标类的Class对象 ------ 它是反射的 "入口",包含了类的所有元信息(属性、方法、构造器等)。

获取 Class 对象的 3 种方式

  1. 对象.getClass ():通过实例获取(已创建对象时使用)
  2. 类名.class:通过类名直接获取(编译时已知类)
  3. Class.forName ("全类名"):通过类的全路径获取(最常用,动态加载类

这三种方式获取的是同一个Class对象(每个类在 JVM 中只有一个 Class 实例)。

直观理解 Class 对象的作用

下面的图展示了 Class 对象作为 "类元信息载体" 的核心作用:

从图中可以清晰看到:Class 对象就像类的 "说明书",反射通过这份说明书,就能操作原本不可见的类成员。

二、反射应用一:动态创建对象

反射创建对象有两种方式:通过无参构造器和通过有参构造器。

1. 通过无参构造器创建对象

步骤:

  1. 获取 Class 对象
  2. 调用newInstance()方法(JDK9 + 推荐用getDeclaredConstructor().newInstance()

2. 通过有参构造器创建对象

步骤:

  1. 获取 Class 对象
  2. 通过getDeclaredConstructor(参数类型...)获取指定构造器
  3. 调用构造器的newInstance(参数值...)创建对象
java 复制代码
public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        Class<?> userClass = Class.forName("com.example.reflection.User");
        
        // 获取有参构造器(参数为String和int)
        Constructor<?> constructor = userClass.getDeclaredConstructor(String.class, int.class);
        
        // 创建对象(传入参数)
        User user = (User) constructor.newInstance("张三", 20);
        System.out.println(user); // 输出:User{name='张三', age=20}
    }
}

对象创建流程可视化

三、反射应用二:调用方法(含私有方法)

反射可以调用类的任意方法,包括私有方法。核心是通过Method类操作。

1. 调用公有方法

步骤:

  1. 获取 Class 对象和实例
  2. 通过getMethod(方法名, 参数类型...)获取公有方法
  3. 调用method.invoke(实例, 参数值...)执行方法
java 复制代码
class User {
    // 公有方法
    public void publicMethod(String msg) {
        System.out.println("公有方法:" + msg);
    }
    
    // 私有方法
    private void privateMethod(int num) {
        System.out.println("私有方法:数字=" + num);
    }
}

public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        Class<?> userClass = User.class;
        User user = (User) userClass.newInstance();
        
        // 调用公有方法
        Method publicMethod = userClass.getMethod("publicMethod", String.class);
        publicMethod.invoke(user, "Hello 反射"); // 输出:公有方法:Hello 反射
    }
}

2. 调用私有方法

私有方法需要额外一步:通过setAccessible(true)绕过访问权限检查(核心操作!)

java 复制代码
public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        Class<?> userClass = User.class;
        User user = (User) userClass.newInstance();
        
        // 调用私有方法
        Method privateMethod = userClass.getDeclaredMethod("privateMethod", int.class);
        // 关键:设置可访问(绕过权限检查)
        privateMethod.setAccessible(true);
        privateMethod.invoke(user, 666); // 输出:私有方法:数字=666
    }
}

私有方法调用的核心逻辑

为什么setAccessible(true)能访问私有方法?看下图的解析:

简单说:setAccessible(true)会关闭 Java 的访问权限检查,让反射可以直接操作私有成员(但会破坏封装性,需谨慎使用)。

四、反射应用三:修改属性(含私有属性)

修改属性的逻辑和调用方法类似,核心是Field类,私有属性同样需要setAccessible(true)

1. 修改公有属性

java 复制代码
class User {
    public String publicField; // 公有属性
    private int privateField;  // 私有属性
}

public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        Class<?> userClass = User.class;
        User user = (User) userClass.newInstance();
        
        // 修改公有属性
        Field publicField = userClass.getField("publicField");
        publicField.set(user, "公有属性值");
        System.out.println(user.publicField); // 输出:公有属性值
    }
}

2. 修改私有属性

java 复制代码
public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        Class<?> userClass = User.class;
        User user = (User) userClass.newInstance();
        
        // 修改私有属性
        Field privateField = userClass.getDeclaredField("privateField");
        privateField.setAccessible(true); // 关键:允许访问私有属性
        privateField.set(user, 100); // 设置值
        
        // 读取私有属性值
        int value = (int) privateField.get(user);
        System.out.println(value); // 输出:100
    }
}

私有属性修改流程

五、反射的利弊与注意事项

优点

  1. 灵活性高:动态操作类,适合框架开发(如 Spring、MyBatis)
  2. 解耦:不依赖编译时类信息,降低代码耦合度
  3. 扩展性强:可通过配置文件动态加载类,无需修改代码

缺点

  1. 性能开销:反射操作绕开了编译期检查,性能比直接调用低(约慢 10-100 倍)
  2. 破坏封装:直接操作私有成员,违反面向对象设计原则
  3. 安全性风险:可能被恶意利用(如绕过权限检查攻击)
  4. 代码可读性差:反射代码较繁琐,不如直接调用直观

注意事项

  • 非必要不使用反射(优先直接调用)
  • 反射操作需处理大量异常(NoSuchMethodExceptionIllegalAccessException等)
  • 对性能敏感的场景(如高频调用)避免使用反射
  • 私有成员操作需谨慎,确保符合业务逻辑

总结:反射的价值与应用场景

反射是 Java 中 "动态性" 的核心体现,虽然有性能和封装性的代价,但在框架开发、动态代理、序列化等场景中不可替代:

  • 框架开发:Spring IOC 容器通过反射创建 Bean,MyBatis 通过反射映射数据库字段
  • 动态代理:AOP 的实现基础(如 Spring AOP)
  • 序列化 / 反序列化:JSON 框架(如 Jackson)通过反射读写对象属性
  • 注解处理 :通过反射解析类 / 方法上的注解(如 JUnit 的@Test

掌握反射,能让你看透 Java 框架的底层逻辑,写出更灵活、更具扩展性的代码。但记住:反射是把 "双刃剑",合理使用才能发挥其最大价值。

最后 :反射的核心是 "运行时获取类信息并操作",关键 API 是ClassConstructorMethodField,而setAccessible(true)是操作私有成员的 "钥匙"。多动手实践,才能真正理解反射的精髓!


原创声明:本文为CSDN博主梵得儿SHI原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

觉得文章对你有帮助?点个赞👍支持一下!有疑问欢迎在评论区讨论~

相关推荐
沐知全栈开发3 小时前
Foundation 折叠列表
开发语言
JAVA学习通3 小时前
Spring AI 核心概念
java·人工智能·spring·springai
望获linux3 小时前
【实时Linux实战系列】实时 Linux 在边缘计算网关中的应用
java·linux·服务器·前端·数据库·操作系统
..Cherry..3 小时前
【java】jvm
java·开发语言·jvm
老K的Java兵器库3 小时前
并发集合踩坑现场:ConcurrentHashMap size() 阻塞、HashSet 并发 add 丢数据、Queue 伪共享
java·后端·spring
冷冷的菜哥4 小时前
go邮件发送——附件与图片显示
开发语言·后端·golang·邮件发送·smtp发送邮件
lly2024064 小时前
Linux 文件与目录管理
开发语言
计算机毕业设计木哥4 小时前
计算机毕业设计选题推荐:基于SpringBoot和Vue的爱心公益网站
java·开发语言·vue.js·spring boot·后端·课程设计
ANnianStriver4 小时前
智谱大模型实现文生视频案例
java·aigc