看到题目,不少初级开发同窗就慌了,我每天 crud,什么时候用过反射呀?并且它竟然还有 bug 的吗?html
其实否则,反射在咱们平常开发中一直陪伴着咱们,若是咱们一点不重视反射的使用,就会不自觉地写出不少没法理解的 bug。今天咱们就来看看增删改查是如何遇到反射bug的!java
重载level方法,入参分别是int和Integer。 若不使用反射,选用哪一个重载方法很清晰,好比:git
Integer.valueOf(“666”)
走Integer重载那反射调用方法也是根据入参类型肯定使用哪一个重载方法吗? 使用getDeclaredMethod
获取 grade
方法,而后传入Integer.valueOf(“36”)
结果是:
由于反射进行方法调用是经过bash
来肯定方法。本例的getDeclaredMethod
传入的参数类型Integer.TYPE
其实表明int
。 因此无论传包装类型仍是基本类型,最终都是调用int入参重载方法。markdown
将Integer.TYPE
改成Integer.class
,则实际执行的参数类型就是Integer了。且不管传包装类型仍是基本类型,最终都调用Integer入参重载方法。oracle
综上,反射调用方法,是以反射获取方法时传入的方法名和参数类型来肯定调用的方法。ide
泛型容许SE使用类型参数替代精确类型,实例化时再指明具体类型。利于代码复用,将一套代码应用到多种数据类型。oop
泛型的类型检测,能够在编译时检查不少泛型编码错误。但因为历史兼容性而妥协的泛型类型擦除方案,在运行时还有不少坑。ui
如今指望在类的字段内容变更时记录日志,因而SE想到定义一个泛型父类,并在父类中定义一个统一的日志记录方法,子类可继承该方法。上线后总有日志重复记录。编码
虽Base.value正确设置为了JavaEdge,但父类setValue调用了两次,计数器显示2
两次调用Base.setValue,是由于getMethods找到了两个setValue
:
setValue(T value)
泛型擦除后是setValue(Object value)
,因而子类入参String的setValue
被看成新方法setValue
未加@Override
注解,编译器未能检测到重写失败有的同窗会认为是由于反射API使用错误致使而非重写失败:
getMethods
获得当前类和父类的全部public
方法
getDeclaredMethods
得到当前类全部的public、protected、package和private方法
因而用getDeclaredMethods
替换getMethods
: 虽然这样作能够规避重复记录日志,但未解决子类重写父类方法失败的问题
setValue
因而,终于明白还得从新实现Sub2,继承Base时将String做为泛型T类型,并使用 @Override 注解 setValue
Sub2的
setValue
居然调用了两次,难道是JDK反射有Bug!getDeclaredMethods
查找到的方法确定来自Sub2
;并且Sub2看起来也就一个setValue,怎么会重复?
调试发现,Child2类其实有俩setValue
:入参分别是String、Object。 这就是由于泛型类型擦除。
Java泛型类型在编译后被擦除为Object。子类虽指定父类泛型T类型是String,但编译后T会被擦除成为Object,因此父类setValue
入参是Object,value也是Object。 若Sub2.setValue想重写父类,那入参也须为Object。因此,编译器会为咱们生成一个桥接方法。 Sub2类的class字节码:
➜ genericandinheritance git:(master) ✗ javap -c Sub2.class
Compiled from "GenericAndInheritanceApplication.java"
class com.javaedge.oop.genericandinheritance.Sub2 extends com.javaedge.oop.genericandinheritance.Base<java.lang.String> {
com.javaedge.oop.genericandinheritance.Sub2();
Code:
0: aload_0
1: invokespecial #1 // Method com/javaedge/oop/genericandinheritance/Base."<init>":()V
4: return
public void setValue(java.lang.String);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String call Sub2.setValue
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: aload_0
9: aload_1
10: invokespecial #5 // Method com/javaedge/oop/genericandinheritance/Base.setValue:(Ljava/lang/Object;)V
13: return
public void setValue(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: checkcast #6 // class java/lang/String
// 入参为Object的setValue在内部调用了入参为String的setValue方法
5: invokevirtual #7 // Method setValue:(Ljava/lang/String;)V
8: return
}
复制代码
若编译器未帮咱们实现该桥接方法,则Sub2重写的是父类泛型类型擦除后、入参是Object的setValue。这两个方法的参数,一个String一个Object,显然不符Java重写。
入参为Object的桥接方法上标记了public synthetic bridge
:
知道了桥接方法的存在,如今就该知道如何修正代码了。
参考