我有两节课
// BaseClass.java
class BaseClass<T> {
public String getTest(){
return "one";
}
public String getTest2(T t){
return "two";
}
public String getTest3(T t){
return "three";
}
}
// OverrideClass.java
public class OverrideClass extends BaseClass<Test>{
}
Run Code Online (Sandbox Code Playgroud)
我尝试运行以下代码
// Test.java
public class Test {
public static void main(String[] args) {
Class<OverrideClass> overrideClass = OverrideClass.class;
Method[] declaredMethods = overrideClass.getDeclaredMethods();
System.out.println(Arrays.toString(declaredMethods));
}
}
Run Code Online (Sandbox Code Playgroud)
我认为它应该输出
[]
Run Code Online (Sandbox Code Playgroud)
但实际上输出是
[public java.lang.String OverrideClass.getTest()]
Run Code Online (Sandbox Code Playgroud)
通过字节码,我认为这是一个桥接方法,但我不知道它为什么会生成,如果我将 BaseClass 公开它就会消失。
// access flags 0x1041
public synthetic bridge getTest()Ljava/lang/String;
L0
LINENUMBER 1 L0
ALOAD 0
INVOKESPECIAL BaseClass.getTest ()Ljava/lang/String;
ARETURN
L1
LOCALVARIABLE this LOverrideClass; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
}
Run Code Online (Sandbox Code Playgroud)
我的问题是:
getTest()
要生成桥接方法?getTest2()
并getTest3()
没有生成他们的桥接方法?这似乎与泛型有关。我已经分析了这个问题,这是结果。我已经简化了问题中的示例。
这个答案解决了OP的第一个问题
如果 BaseClass 是默认值,为什么 getTest() 会生成桥接方法?
对于关于泛型出现不一致的第二个问题,您可以阅读Denis 的回答
class BaseClass {
public String getTest(){
return "one";
}
public String getTest2(){
return "two";
}
public String getTest3(){
return "three";
}
}
public class OverrideClass extends BaseClass{}
public class Application {
public static void main(String[] args) throws Exception {
Class<OverrideClass> overrideClass1 = OverrideClass.class;
Method[] declaredMethods1 = overrideClass1.getDeclaredMethods();
System.out.println(Arrays.toString(declaredMethods1));
}
}
Run Code Online (Sandbox Code Playgroud)
使用 JDK 8 或 JDK 17 执行此操作始终具有相同的结果
[public java.lang.String OverrideClass.getTest(), public java.lang.String OverrideClass.getTest2(), public java.lang.String OverrideClass.getTest3()]
Run Code Online (Sandbox Code Playgroud)
只需将上面的例子修改为
public class BaseClass {
public String getTest(){
return "one";
}
public String getTest2(){
return "two";
}
public String getTest3(){
return "three";
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,更改发生在基类的访问修饰符中,该修饰符现在是公共的!
执行此操作会产生预期的行为 []
然而,这不是 JDK 的错误。本来就是这样的。
getDeclaredMethods()
在 example1 中,返回父类的相同方法的原因并不是因为这些方法被打印为继承的。这是因为这些是实际上属于该子类(OverrideClass)的桥接方法。
这个功能很久以前就被添加了,你可以在这里看到Oracle 开发人员的解释是
建议在这些非常罕见的情况下添加桥接方法,以修复反射问题,而无需其他预见的修复或解决方法。具体来说,当公共方法从非公共类继承到公共类时,我们将生成一个桥接方法。
正如您还可以在这里看到的,Oracle 开发人员的最新评论是
在这样的情况下添加桥接方法,其中公共类公共方法来自非公共超类,以允许反射访问子类方法(JDK-6342411)。
关闭这个问题不是一个错误。
因此,这种情况仅发生在非公共父类中,因为在这种情况下,需要将继承的公共方法添加为该子类中的桥接方法。
在示例 2中,桥接方法不存在,如果您尝试使用打印出反汇编代码,javap -c OverrideClass
您将看到以下内容
public class OverrideClass extends BaseClass {
public OverrideClass();
Code:
0: aload_0
1: invokespecial #1 // Method BaseClass."<init>":()V
4: return
}
Run Code Online (Sandbox Code Playgroud)
在存在桥接方法的示例 1中,如果您尝试使用打印出反汇编代码,javap -c OverrideClass
您将看到以下内容
public class OverrideClass extends BaseClass {
public OverrideClass();
Code:
0: aload_0
1: invokespecial #1 // Method BaseClass."<init>":()V
4: return
public java.lang.String getTest3();
Code:
0: aload_0
1: invokespecial #7 // Method BaseClass.getTest3:()Ljava/lang/String;
4: areturn
public java.lang.String getTest2();
Code:
0: aload_0
1: invokespecial #11 // Method BaseClass.getTest2:()Ljava/lang/String;
4: areturn
public java.lang.String getTest();
Code:
0: aload_0
1: invokespecial #14 // Method BaseClass.getTest:()Ljava/lang/String;
4: areturn
}
Run Code Online (Sandbox Code Playgroud)
如果 BaseClass 是默认值,为什么 getTest() 会生成桥接方法?
为什么 getTest2() 和 getTest3() 没有生成它们的桥接方法?这似乎与泛型有关。
这是一个错误,来自JDK-8216196 的描述。没有为泛型方法创建可见性桥:
如果方法是公共的但由包私有类声明,则 javac 通常会为方法添加可见性桥:
如果方法具有泛型类型,则情况并非如此:
该错误已由JDK-8203488 修复。从 TransTypes 中删除错误生成,向后移植到 11.0.1
package org.example;
class BaseClass<T> {
public String getTest() {
return "one";
}
public String getTest2(T t) {
return "two";
}
public String getTest3(T t) {
return "three";
}
}
Run Code Online (Sandbox Code Playgroud)
package org.example;
public class OverrideClass extends BaseClass<Void> {
}
Run Code Online (Sandbox Code Playgroud)
package org.example.other; // Note other package
import org.example.OverrideClass;
import java.lang.reflect.Method;
import java.util.Arrays;
public class OverrideClass2 extends OverrideClass {
public static void main(String[] args) throws Exception {
Arrays.stream(OverrideClass.class.getDeclaredMethods())
.forEach(System.out::println);
invokeGetTestReflectively();
System.out.println("invoked 'getTest' reflectively");
invokeGetTest2Reflectively();
System.out.println("invoked 'getTest2' reflectively");
}
private static void invokeGetTestReflectively() throws Exception {
Method method = OverrideClass2.class.getMethod("getTest");
method.invoke(new OverrideClass2());
}
private static void invokeGetTest2Reflectively() throws Exception {
Method method = OverrideClass2.class.getMethod("getTest2", Object.class);
method.invoke(new OverrideClass2(), new Object[]{null});
}
}
Run Code Online (Sandbox Code Playgroud)
public java.lang.String org.example.OverrideClass.getTest()
invoked 'getTest' reflectively
Exception in thread "main" java.lang.IllegalAccessException: Class org.example.other.OverrideClass2 can not access a member of class org.example.BaseClass with modifiers "public"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
at java.lang.reflect.Method.invoke(Method.java:491)
at org.example.other.OverrideClass2.invokeGetTest2Reflectively(OverrideClass2.java:26)
at org.example.other.OverrideClass2.main(OverrideClass2.java:15)
Run Code Online (Sandbox Code Playgroud)
public java.lang.String org.example.OverrideClass.getTest()
public java.lang.String org.example.OverrideClass.getTest2(java.lang.Object)
public java.lang.String org.example.OverrideClass.getTest3(java.lang.Object)
invoked 'getTest' reflectively
invoked 'getTest2' reflectively
Run Code Online (Sandbox Code Playgroud)