重载grade方法,入参分别是int和Integer。
不通过反射,选用哪个重载方法很清晰,比如传入666走int参数重载方法,传入Integer.valueOf(“666”)
走Integer重载。
但你若墨守成规认为反射调用方法也是根据入参类型确定方法重载。
使用getDeclaredMethod
获取 grade
方法,然后传入Integer.valueOf(“36”)
因为通过反射进行方法调用都是
本例的getDeclaredMethod
传入的参数类型Integer.TYPE
其实一直代表int
所以实际执行方法时不管传包装类型/基本类型,最终都是调用int入参的grade方法。
将Integer.TYPE
改为Integer.class
,实际执行的参数类型就是Integer了。且无论传包装类型/基本类型,最终都会调用Integer为入参的grade方法。
所以反射调用方法,是以反射获取方法时传入的方法名和参数类型来确定调用的方法。
泛型是一种编程范式,允许开发者使用类型参数替代精确类型,实例化时再指明具体类型。也利于代码重用,将一套代码应用到多种数据类型。
泛型的类型检测,可以在编译时暴露大多数泛型编码错误。但由于历史兼容性而妥协的泛型类型擦除,在运行时才会暴露很多坑。
期望在类字段内容变动时记录日志,于是开发同学就想到定义一个泛型父类,并在父类中定义一个统一的日志记录方法,子类可继承该方法。上线后总出现日志重复记录问题。
String
而非T
的setValue
。期望覆盖父类的setValue实现。
子类方法的调用是通过反射。
虽Parent的value字段正确设置JavaEdge,但父类setValue调用了两次,计数器而显示2
两次Parent的setValue方法调用,是因为getMethods找到了两个setValue
的,分属于父类/子类。
setValue(T value)
泛型擦除后是setValue(Object value)
,于是子类入参String的setValue
被当作了新方法setValue
方法未加@Override
注解,编译器未能检测到重写失败。重写子类方法时,务必使用@Override注解。
但有人认为问题是反射API使用不当而未意识到重写失败。查文档后才发现
getMethods
能获得当前类和父类的所有public
方法getDeclaredMethods
仅获得当前类所有的public、protected、package和private方法于是用getDeclaredMethods
替换getMethods
:
当其他人使用Child1时还是会发现有俩setValue
,让人困惑。
重新实现Child2,继承Parent时String作为泛型T类型,并使用@Override
注解setValue
,实现有效的方法重写
Child2的setValue
调了两次。难道是JDK的反射出Bug了!
通过getDeclaredMethods
查找到的方法肯定来自Child2
本身;而且Child2类中看起来也只有一个setValue,怎么可能还重复?
调试发现,Child2类其实有俩setValue
:入参分别是String/Object。
这就是泛型类型擦除导致。
Java泛型类型在编译后被擦除为Object。子类虽指定父类泛型T类型是String,但编译后T会被擦除成为Object,所以父类setValue
入参是Object,value也是Object。
若Child2 setValue
想覆盖父类,那入参也须为Object。所以,编译器会为我们生成一个桥接方法
Child2类的class字节码:
Compiled from "GenericAndInheritanceApplication.java"
class Child2 extends Parent<java.lang.String> {
Child2();
Code:
0: aload_0
1: invokespecial #1 // Method Parent."":()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 Child2.setValue called
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: aload_0
9: aload_1
10: invokespecial #5 // Method Parent.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
}
若编译器未帮我们实现该桥接方法,那Child2重写的是父类泛型类型擦除后、入参是Object的setValue。这两个方法的参数,一个String一个Object,明显不符合Java语义:
class Parent {
AtomicInteger updateCount = new AtomicInteger();
private Object value;
public void setValue(Object value) {
System.out.println("Parent.setValue called");
this.value = value;
updateCount.incrementAndGet();
}
}
class Child2 extends Parent {
@Override
public void setValue(String value) {
System.out.println("Child2.setValue called");
super.setValue(value);
}
}
使用method的isBridge方法,来判断方法是不是桥接方法:
Arrays.stream(child2.getClass().getDeclaredMethods())
.filter(method -> method.getName().equals("setValue") && !method.isBridge())
.findFirst().ifPresent(method -> {
try {
method.invoke(chi2, "test");
} catch (Exception e) {
e.printStackTrace();
}
});
使用反射查询类方法清单时:
参考