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 版权协议,转载请附上原文出处链接及本声明。

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

相关推荐
一位搞嵌入式的 genius25 分钟前
前端实战开发(四):从迭代器到异步编程:ES6 Generator 全面解析 + 实战问题排查
开发语言·前端·es6·前端实战
来来走走27 分钟前
Android开发(Kotlin) 高阶函数、内联函数
android·开发语言·kotlin
Murphy_lx37 分钟前
C++ thread类
开发语言·c++
彩妙不是菜喵1 小时前
C++ 中 nullptr 的使用与实践:从陷阱到最佳实践
开发语言·jvm·c++
L.EscaRC1 小时前
Spring Boot 自定义组件深度解析
java·spring boot·后端
pengzhuofan1 小时前
IntelliJ IDEA 常用快捷键
java·ide·intellij-idea
ANGLAL1 小时前
17.MyBatis动态SQL语法整理
java·sql·mybatis
lskisme1 小时前
springboot maven导入本地jar包
开发语言·python·pycharm
SheepHappy1 小时前
MyBatis-Plus 源码阅读(二)代码生成器原理深度剖析
java·源码阅读