如果超类是默认的, getDeclaredMethods() 返回继承的方法

shi*_*085 13 java generics

我有两节课

// 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)

我的问题是:

  1. 如果 BaseClass 是默认的,为什么getTest()要生成桥接方法?
  2. 为什么getTest2()getTest3()没有生成他们的桥接方法?这似乎与泛型有关。

Pan*_*kos 8

我已经分析了这个问题,这是结果。我已经简化了问题中的示例。

这个答案解决了OP的第一个问题

如果 BaseClass 是默认值,为什么 getTest() 会生成桥接方法?

对于关于泛型出现不一致的第二个问题,您可以阅读Denis 的回答

实施例1

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)

实施例2

只需将上面的例子修改为

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)


cac*_*co3 5

  1. 如果 BaseClass 是默认值,为什么 getTest() 会生成桥接方法?

    请参阅Panagiotis Bougioukos 的回答

  2. 为什么 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)

运行结果

Java 8,openjdk 版本“1.8.0_332”
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)
  • 允许反射访问,但 javac 中存在错误
Java 17,openjdk 版本“17.0.3”2022-04-19 LTS
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)
  • 允许反射访问,javac 中没有错误