【问题标题】:getDeclaredMethods() return inherited methods if superclass is default如果超类是默认的,getDeclaredMethods() 返回继承的方法
【发布时间】:2022-06-19 17:15:31
【问题描述】:

我有两节课

// 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>{
}
 

我尝试运行以下代码

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

我认为它应该输出

[]

但实际上输出是

[public java.lang.String OverrideClass.getTest()]

通过字节码,我认为这是一个桥接方法,但我不知道它为什么会生成,如果我将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
}

我的问题是:

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

【问题讨论】:

  • 我得到了所有 3 个getTest 方法。您确定在描述您的情况时没有遗漏任何内容吗?
  • 您使用的是什么 Java 版本?
  • @Sweeper 我确定,只有在不是泛型的情况下,我才能得到getTestmethod 的全部 3 个
  • @Lino 我的 Java 版本是 1.8.0_281
  • @Lino 虽然我在 Java 18.0.1 上看到了同样的事情

标签: java generics


【解决方案1】:

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

这个答案处理OP的第一个问题

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

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

示例 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));
   }
}

无论是使用 JDK 8 还是使用 JDK 17 执行此操作,结果总是相同

[public java.lang.String OverrideClass.getTest(), public java.lang.String OverrideClass.getTest2(), public java.lang.String OverrideClass.getTest3()]

示例 2

只需将上面的例子修改成

public class BaseClass {

    public String getTest(){
        return "one";
    }

    public String getTest2(){
        return "two";
    }
    public String getTest3(){
        return "three";
    }
}

请注意,更改发生在 Base 类的访问修饰符中,现在是 public

执行此操作会产生[] 的预期行为

然而,这不是 JDK 的错误。本来就是这样的。

说明

在示例 1 中 getDeclaredMethods() 返回父类的相同方法的原因不是因为这些方法被打印为继承。这是因为那些实际上属于该子类(OverrideClass)的桥接方法。

这个功能是很久以前添加的,你可以从 oracle 的开发者看到here 的解释是

建议是在这些非常罕见的情况下添加桥接方法来修复 没有其他可预见的修复或解决方法的反射问题。 具体来说,当一个公共方法时,我们会生成一个桥接方法 从非公共类继承到公共类。

您还可以看到 here,来自 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
    }

在存在桥接方法的示例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
}

【讨论】:

    【解决方案2】:
    1. 如果 BaseClass 是默认的,为什么 getTest() 会生成桥接方法?

      Panagiotis Bougioukos' answer

    2. 为什么 getTest2() 和 getTest3() 没有生成它们的桥接方法?这似乎与泛型有关。

      这是一个错误,来自JDK-8216196. No visibility bridges are created for generic methods的描述:

      如果方法是公共的但由包私有类声明,javac 通常会为方法添加可见性桥:

      如果方法具有泛型类型,则情况并非如此:

    该错误已由JDK-8203488. Remove error generation from 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";
        }
    }
    
    package org.example;
    
    public class OverrideClass extends BaseClass<Void> {
    }
    
    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});
        }
    }
    

    运行结果

    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)
    
    • 允许反射访问,但 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
    
    • 允许反射访问,javac 中没有错误

    【讨论】:

    • 第 1 部分之前已经在我发布的答案中再次使用相同的引用链接和来自 Oracle 开发人员的相同引用进行了回答。最好像答案的第 2 部分那样为线程添加额外的价值,这是 OP 询问泛型的有效案例。
    • @PanagiotisBougioukos,在第一部分引用了您的答案,而将第二部分保留为原样
    • 还提到了您对泛型问题的回答,您的回答很好地涵盖了这一点。还对内容和保持线程尽可能好以便 OP 轻松阅读的答案表示赞同
    猜你喜欢
    • 2022-12-20
    • 1970-01-01
    • 2011-05-04
    • 1970-01-01
    • 2019-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-04
    相关资源
    最近更新 更多