浅聊Java反射

Java 的反射(Reflection)机制是 Java 语言中最强大、但也最容易让初学者感到迷惑的特性之一。

如果用一句话概括反射:它允许程序在运行期间(Runtime),像照镜子一样,动态地获取任意一个类的内部结构(包括属性、方法、构造器),并且能够动态地操作这些属性和方法。

为了让你由浅入深地理解,我们分为四个阶段来拆解:概念认知 -> 获取"镜子" -> 核心操作 -> 底层意义


第一阶段:为什么要用反射?(打破常规)

在正常情况下,我们写代码是"正向"的。也就是先知道有什么类,然后去 new 它,调用它的方法。

Java

复制代码
// 正向操作(编译期就已经确定了)
User user = new User();
user.setName("张三");
user.sayHello();

但假设有一天,你正在写一个框架(比如 Spring)。你不知道未来使用这个框架的程序员会写什么类,你只知道他们会在一个配置文件(比如 XML 文件)里写下一串字符串:class="com.example.User"

这时候你没法在代码里写 new User(),因为你写框架的时候 User 类根本不存在! 反射就是为了解决这种"在运行时才确定要操作什么类"的问题。 它赋予了 Java 极大的动态性。


第二阶段:拿到类的"解剖图" ------ Class 对象

在 Java 中,万物皆对象。连"类"本身也是一种对象,这种对象叫做 Class 对象。你想反射操作一个类,第一步必须先拿到它的 Class 对象(也就是它的解剖图)。

拿到 Class 对象有三种常见方式:

Java

复制代码
// 假设有一个现成的 User 类
// 方式一:通过类名(最常用,适合硬编码)
Class<?> clazz1 = User.class;

// 方式二:通过实例对象(知道对象,想看看它属于什么类)
User user = new User();
Class<?> clazz2 = user.getClass();

// 方式三:通过类的全限定名字符串(最动态,框架最爱,适合读取配置文件)
Class<?> clazz3 = Class.forName("com.example.User"); 

拿到 clazz 之后,你就可以对 User 类进行"解剖"了。


第三阶段:核心操作演练(用代码说话)

为了演示,我们先准备一个普通的 User 类,注意里面有一些私有(private) 的属性和方法。

Java

复制代码
public class User {
    private String name; // 私有属性
    public int age;

    // 无参构造
    public User() { }

    // 带参构造
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 私有方法
    private void secretMethod() {
        System.out.println(name + " 的秘密方法被调用了!");
    }

    // 公开方法
    public void publicMethod() {
        System.out.println("大家好,我是 " + name);
    }
}

接下来,我们全程用反射来操作它(就像黑客一样绕过常规限制)。

1. 动态创建对象

不使用 new 关键字,用反射实例化对象。

Java

复制代码
Class<?> clazz = Class.forName("com.example.User");

// 1. 调用无参构造创建对象
Object obj1 = clazz.getDeclaredConstructor().newInstance();

// 2. 调用带参构造创建对象
Object obj2 = clazz.getDeclaredConstructor(String.class, int.class)
                   .newInstance("李四", 25);
2. 暴力篡改私有属性(反射的霸道之处)

正常情况下,外界是无法修改 private 属性的,但反射可以。

Java

复制代码
Object userObj = clazz.getDeclaredConstructor().newInstance();

// 获取名为 "name" 的私有属性
Field nameField = clazz.getDeclaredField("name");

// 【核心魔术】打破封装!允许访问私有成员
nameField.setAccessible(true);

// 将 userObj 这个对象的 name 属性设置为 "王五"
nameField.set(userObj, "王五");

// 验证一下
System.out.println(nameField.get(userObj)); // 输出: 王五
3. 动态调用私有方法

同样,私有方法也能被强行调用。

Java

复制代码
// 获取名为 "secretMethod" 的私有方法(没有参数)
Method secretMethod = clazz.getDeclaredMethod("secretMethod");

// 打破封装
secretMethod.setAccessible(true);

// 执行 userObj 这个对象上的 secretMethod 方法
secretMethod.invoke(userObj); 
// 输出: 王五 的秘密方法被调用了!

第四阶段:由深入浅看反射(它的优缺点与应用场景)

看完了上面的代码,你可能会觉得:"这不仅麻烦,破坏了面向对象的封装性(private 形同虚设),而且每次都要抛出一大堆异常(ClassNotFoundException, IllegalAccessException 等)。"

为什么要用这么"危险"的东西?

应用场景:无处不在的反射

日常写业务代码(增删改查),你几乎永远用不到反射 。但是,一旦你开始接触或者编写底层框架,反射就是灵魂:

  1. Spring 框架 (IoC/DI): Spring 怎么知道给你注入哪个对象?它就是通过读取 XML 或注解(如 @Service, @Autowired),获取全类名,然后通过反射 Class.forName().newInstance() 帮你把对象 new 出来并放进容器里的。

  2. JDBC 数据库连接: Class.forName("com.mysql.cj.jdbc.Driver"); 这就是典型的反射加载驱动。

  3. MyBatis: 你只写了接口(Mapper),没有写实现类,为什么能查数据库?因为 MyBatis 在底层用反射和动态代理帮你动态生成了实现类。

  4. 各种 JSON 序列化库(如 Gson, FastJson): 它们怎么知道把你传进去的 JSON 字符串转成什么对象的?也是通过反射读取对象的字段去赋值的。

反射的"代价"
  • 性能开销: 反射是一种"解释性"操作,Java 虚拟机无法对这类代码进行早期的性能优化。因此,反射操作通常比直接的 Java 代码慢得多。(不过在现代 JVM 中,这种性能损耗在绝大多数常规业务场景下可以忽略不计)。

  • 破坏安全性: 由于可以强制访问私有变量和方法,可能会导致不可预知的副作用,破坏了类的内部逻辑约束。

总结一下: 反射就像是 Java 提供给开发者的一把"万能钥匙"和"X光机"。业务开发时把钥匙收好(遵守面向对象规则),框架开发时拿出钥匙(追求极致的灵活性和解耦)。

相关推荐
像我这样帅的人丶你还1 小时前
Java 后端详解(三):全局异常处理与 JPA 数据库映射
java·后端
NE_STOP1 小时前
vibe Coding -- 小项目实战
java
未秃头的程序猿7 小时前
Java 26正式发布!这3个新特性,让代码量直接减半
java·后端·面试
用户298698530147 小时前
Word 文档文本查找与替换的 Java 实现方案
java·后端
阿哉7 小时前
Nacos 服务发现源码:藏在背后的两套事件机制,90%的人只讲了一半
java
咖啡八杯8 小时前
GoF设计模式——命令模式
java·设计模式·架构
AI人工智能_电脑小能手8 小时前
【大白话说Java面试题 第125题】【并发篇】第25题:说说 Java 线程的中断机制
java·后端·面试
Java内核笔记8 小时前
Spring Security 源码解析(六)无状态 JWT 实践:Session 共享与自定义过滤器
java·后端
荣码8 小时前
LangGraph多Agent协作:3个Agent干活比1个强,但我踩了4个坑
java·python
唐青枫9 小时前
Java 虚拟线程实战指南:从 Thread API 到 Spring Boot 高并发应用
java