看到标题,许多初级开发同学就慌了,我天天 crud,何时用过反射呀?而且它居然还有 bug 的吗?
其实不然,反射在咱们日常开发中一向陪伴着咱们,假如咱们一点不重视反射的运用,就会不自觉地写出许多无法了解的 bug。今天咱们就来看看增修改查是怎样遇到反射bug的!
1 当反射遇见重载
重载level办法,入参分别是int和Integer。
若不运用字节码反射,选用哪个重载办法很明晰,比方:
- 传入666就走int参数重载
- 传入
Integer.valueOf(“666”)
走Integer重载
那反射调用办法也是根据入参类型供认运用哪个重载办法吗?
运用getDeclaredMethod
获取 grade
办法,然后传入Integer.valueOfappreciate(“java难学吗36”)
作用是:
由于反射进行办法调用实例化目标有几种办法是经过
办法签名
来供认办法。本例的getDeclaredMethod
传入的参数类型Integer.TYappstorePE
其实代表int
。
所以不管传包装类型仍github是干什么的是根柢类型,毕appetite竟都是调用int入参重载办法。
将Integer.TYPE
改为Integer.class
,则实际实行的参数类型便是Integer了。且不管传包装类型仍是根柢类型github是干什么的,究竟都调用Igitlabntegeappearancer入参重载办法。
综上,反射调用办法,是以反射获取办法时传入的办法名和参数类型来供认调字节码文件是与渠道无关的什么文件用的办法。
2 泛型的类型擦除
泛型容许SE运用类型参数代替准确类型,实例化时再指明详细类型。利于代码复用,将一套代码应用到java初学多种数据类型。
泛型的类型检测,能够在编译时查看许多泛型编码差错。但由于前史兼容性而退让的泛型类型擦除计划,在运行时还有许多坑。
案例
现在期望在类的字段内容变动时记载日志,所以SE想到界说一个实例化数组泛型父类,并在父类中界说字节码文件一个共同的日志记载办法,子类可继承该办法。上线后总有日志重复记载。
- 父类
- 子类1
- 经过反射调用子类办法:
虽Base.value正确设置为了JavaEdge,但父类giti轮胎是什么品牌se实例化一个类tValue调用了两次,计数器显现2
两次调用Base.setValue,是由于getMethods找到了两个setValue
:
子类重写父类办法失利原因
- 子类未指定String泛型参数,父类的泛型办法
setValue(T value)
泛型擦除后是setValue(Ob字节码ject value)
,所以子类入参String的setValue
被当作新办法 - 子类的
setValue
未加@Override
注解,编译器未能检测到重写失利
有的同学会以为是由于反Git射API运用差错导致而非重写失利java言语:
getMethods
得到其时类和父类的全github部public
办法
ge字节码文件的扩展名是什么tDeclaredMgithubethods
获得其时类悉数的public、protected、package和privat字节码e办法
所以用getDeclaredMethods
替换getMethods
:
尽管这样做能够躲避重复记载日志,但未处application理子类重写父类办法失利的问题
- 运用Sub1时仍是会发现有俩个
setValuegiti
所以,总算了解还得从头完结Sub2,继承Bas字节码文件是与渠道无关的二进制码e时将String作为泛型T类型,并运用 @Ove字节码文件是与渠道无关的什么文件rridjava模拟器e 注解 setVjava怎样读alue
- 但仍appetite是呈现重复日志
Sub2的setValue
居然调用了两次,莫非是JDK反射有Bug!getDeclaredMethods
查找到的办法肯定来自Sub2
;而且Sub2看起来也就实例化目标有几种办法一个setValue,怎样application会重复?
调试发现,实例化类Child2类其实appreciate有俩setValue
:入参分别是String、Object。
这便是由于泛型类型擦除字节码是虚拟机的机器码。
反射下的泛型擦除“天坑”
Java泛型类型在编译后被擦除字节码目标为Object。子类虽指定github中文官网网页父类泛型T类型是String,但编译后T会被擦除成为Object,所以父类github中文官网网页setValue
入参是字节码文件是与渠道无关的什么文件Object,valappleue也是Object。
若Sub2.setValue想重写父类,那入参也须为Object。所以,编译器会为咱们生成一个桥接办法。
Sub2类的cappearancelass字节码:
➜ genericanjava怎样读dinheritance git:(master) ✗ javap -c Sub2.class
Compiled from "GenericAndInheritanceApplication.java"
class com.javaedg字节码是虚拟机的机器码e.oop.genericandinheritance.Sub2 extends com.javJavaaedge.oop.genericandinheritance.实例化Base<java.lang.String> {
com.javaedggitlabe.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/PrintappearStream.println:(Lj字节码文件ava/lan字节码文件是与渠道无关的什么文件g/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 se实例化目标有几种办法tValue(java.lang.Object);
Cappetiteode:
0: aload_0
1: aload_1
2: checkcast #6 //实例化数组 class java/lang/String
// 入参为Object的字节码是虚拟机的机器码s实例化类etValue在内部调用了入参为St字节码文件的扩展名是什么ring的setValue办法
5: invokevirtual #7 // Mjava就业培训班ethod setValue:(Ljava/lang/giti轮胎是什么品牌Sjavaapi中文在线看tring;)V
8git指令: return
}
若编译器未帮咱们完结该桥接办法,则Sub2实例化类重写的是java模拟器父类泛型类型擦除后、入参是Objec实例化数组t的setValue。这两个办法的参数,一个String一个Object,显着不符Java重写。
入参为Object的桥接办法上标记了public synthetic bridge
:
- synthetic代表Git由编译器生成字节码和机器码的差异的不行见代码
- bridge代字节码和机器码的差异表这是泛型类型擦除后生成的桥接代码
修改
知道了桥接办法的存在,现在就该知道怎样修改代码了。
- 经过getDeclaredMethods获取悉数办法后,还得加上非isBridge这个过滤条件:
- 作用
参阅
- docs.oracle.com/javase/8/do…
- docs.oracle.giteecom/javase/tuto…
- docs.oracle.com/javase/8/do…
- docs.oracle.com/javase/tuto…
- dogithub中文官网网页cs.oracle.com/javase/8/do…
- docs.oracle.com/javase/tuto…