反射的初步理解

5 阅读3分钟

在正常写代码时,我们习惯了“对象.方法() ”这种顺理成章的调用方式。但反射完全把这个逻辑反转了过来。


1. 核心思维转换:从“主动驱动”到“被动执行”

  • 常规编程(正向思维) :你手里拿着一个“遥控器(对象)”,按下了上面的“按键(方法)”。

    • 语法:遥控器.按下按键("参数")
  • 反射编程(逆向思维) :你手里直接拿着一个孤零零的“按键(Method)”,然后你去找个“遥控器(对象)”把它装上去,再按下去。

    • 语法:按键.执行(遥控器, "参数")

invoke 这个单词在英语里就是“唤醒、祈求、执行”的意思。在 Java 中,invoke 就是那个**“按下去”**的动作。


2. 极简实战:用代码看透 invoke

假设我们有一个极其简单的 Programmer(程序员)类:

Java

public class Programmer {
    
    // 1. 普通的实例方法(每个人写的语言不同)
    public void typeCode(String language) {
        System.out.println("正在狂敲代码,语言是:" + language);
    }

    // 2. 静态方法(全人类通用的规则,不需要实例化)
    public static void drinkCoffee() {
        System.out.println("吨吨吨...补充能量");
    }
}

现在,我们来看看“常规调用”和“用 invoke 强行调用”到底有什么区别。

场景 A:调用普通的实例方法 (typeCode)

【常规写法】

Java

Programmer xiaoMing = new Programmer();
xiaoMing.typeCode("Java"); // 正常人这么写

【反射 invoke 写法】

Java

// 1. 拿到“按键” (获取 Method 对象)
Class<?> clazz = Programmer.class;
Method typeMethod = clazz.getDeclaredMethod("typeCode", String.class);

// 2. 准备“遥控器” (准备实例对象)
Object xiaoMing = clazz.newInstance(); // 相当于 new Programmer()

// 3. 按下按键!(执行 invoke)
// 参数 1:必须传入 xiaoMing,因为你要明确是“哪一个程序员”在敲代码!
// 参数 2:"Java" 就是传给 typeCode 方法的实际参数。
typeMethod.invoke(xiaoMing, "Java"); 

深入理解

如果你写成 typeMethod.invoke(null, "Java"),JVM 直接就会抛出 NullPointerException 或者 IllegalArgumentException。为什么?因为 JVM 会骂人:“你让我执行敲代码的动作,但你没告诉我是谁在敲!我怎么敲?”

场景 B:调用静态方法 (drinkCoffee)

【常规写法】

Java

Programmer.drinkCoffee(); // 静态方法直接用类名调,不需要 new 对象

【反射 invoke 写法】

Java

// 1. 拿到“按键”
Method coffeeMethod = clazz.getDeclaredMethod("drinkCoffee");

// 2. 按下按键!(执行 invoke)
// 划重点:第一个参数传了 null!
// 为什么?因为喝咖啡在这个类里是 static 的,它不属于任何一个具体的程序员。
coffeeMethod.invoke(null); 

深入理解

当你把第一个参数传 null 时,就是在明确告诉 JVM:“这个方法是个公共设施(静态方法),你不需要找具体的对象,直接给我执行它原本的逻辑就行了。”


3. invoke 的底层到底干了什么?(JVM 视角)

作为一个了解过 JVM 原理的开发者,你可以这样理解 invoke

正常调用 xiaoMing.typeCode() 时,编译器在编译期就会检查 xiaoMing 是不是 Programmer,有没有这个方法。

invoke 是在运行期,绕过了编译器的所有安全检查。当你调用 typeMethod.invoke(xiaoMing, "Java") 时:

  1. JVM 内部会进行类型校验:检查 xiaoMing 这个对象到底包不包含 typeMethod 这个方法的指令码。
  2. 检查你传的参数 "Java" 的类型和数量对不对得上。
  3. 如果都对,JVM 会动态地把执行流跳转到该方法在内存中的入口地址,执行完毕后,再把返回值包装成一个 Object 扔给你。

4. 总结公式

Method.invoke(Object obj, Object... args) 死死印在脑子里:

动作参数 1 (obj)参数 2 (args)解释
执行实例方法具体的实例对象(不能是 null)方法需要的参数告诉机器:来执行,参数是什么。
执行静态方法null方法需要的参数告诉机器:不需要对象,直接用类执行,参数是什么。

通过这个 Programmer 的例子,有没有感觉 invoke 其实就是一个“脱离了具体对象、独立存在的动作执行器”?

理解了公开方法的调用后,想不想看看怎么用反射结合 invoke 强行破解并调用类里面被 private 藏起来的私有方法?