目录

精简版(核心逻辑)

详细版(逐行剖析反射如何"作弊")

背景:MyArray类长这样

反射绕过过程(对应代码行)

对比图:正常调用 vs 反射调用

后果演示(为什么要避免这样做)


这个例子展示的是"编译器的墙被反射打了个洞"。让我拆解给你看:

精简版(核心逻辑)

// 【正常写法】编译器严格把关,类型错误直接报错
myArray.add("字符串");  // ❌ 编译失败!编译器:你这是Integer容器!

// 【反射写法】编译器看不到反射里的操作,运行时JVM也不管类型
Method addMethod = MyArray.class.getMethod("add", Object.class);  // 第1行
addMethod.invoke(myArray, "字符串");  // 第2行:✅ 运行成功!
// 结果:"字符串"真的被塞进了本该存Integer的myArray里!

关键结论:反射是运行时的"后门",编译器检查不到第2行传的是String,JVM运行期也无法阻止(因为内部就是Object[])。

详细版(逐行剖析反射如何"作弊")

背景:MyArray类长这样

public class MyArray<T> {
    private Object[] data = new Object[10];  // 运行期T变成Object
    
    public void add(T item) {  // 运行期变成:add(Object item)
        data[0] = item;
    }
    
    public T get(int index) {  // 运行期变成:get()返回Object
        return (T) data[index];
    }
}

反射绕过过程(对应代码行)

// 第1行:通过反射获取add方法
// 【关键点】getMethod第一个参数是方法名"add"
// 第二个参数Object.class是擦除后的参数类型!
// 因为运行期add方法的签名实际上是:add(Object item),不是add(Integer item)
Method addMethod = MyArray.class.getMethod("add", Object.class);
// 此时addMethod指向的是"裸方法",它接受任何Object,不限于Integer

// 第2行:invoke调用方法
// 第一个参数myArray:要操作的对象(本该存Integer的那个)
// 第二个参数"字符串":实际传入的值(类型是String)
addMethod.invoke(myArray, "字符串");

// 【编译器视角】:这行代码编译时完全合法!
// 因为编译器只看到"invoke(Object obj, Object... args)"
// 它不知道你传的是myArray,也不知道"字符串"会进Integer容器
// 编译器:我只是看到反射调用,运行时的事我管不了

// 【JVM运行期视角】:
// 调用myArray.add(Object item),传入"字符串"
// 内部执行:data[0] = "字符串";  // 完全合法,因为data是Object[]
// JVM:我又不知道这个MyArray"应该"存Integer,擦除后就是Object[]

对比图:正常调用 vs 反射调用

维度 正常调用 myArray.add("str") 反射调用 addMethod.invoke(myArray, "str")

编译期

❌ 报错

编译器看到<Integer>,拒绝String

✅ 通过

编译器只看到反射API,无法检查具体参数

运行期

不会执行到

✅ 执行成功

JVM看到的是add(Object),String也是Object,合法

类型安全

编译器保证

❌ 无保障

你可能把"字符串"存进Integer容器,取出时抛出ClassCastException

后果演示(为什么要避免这样做)

// 接上面反射插入"字符串"后:
MyArray<Integer> myArray = new MyArray<>();

// 通过反射塞入String(运行期成功)
addMethod.invoke(myArray, "我不是整数");

// 正常取出(编译器认为这是安全的,因为声明是<Integer>)
Integer num = myArray.get(0);  
// 💥 运行时报错!ClassCastException: String cannot be cast to Integer
// 原因:get()方法里有编译器插入的强制转换 (Integer) data[0]
// 但data[0]实际是String,强转失败!

一句话总结:反射就像是在运行时偷偷摸摸操作,编译器在第36行建立的<Integer>规则管不到反射的"后门",JVM运行期又因为擦除机制分不清类型,结果就能把"字符串"硬塞进Integer专用盒子,等到取出来用时才发现炸了。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐