reflection-method::getgenericreturntype无泛型-可见性

jgwigjjp  于 2021-06-27  发布在  Java
关注(0)|答案(2)|浏览(429)

说明

我有一个奇怪的问题,方法::getgenericreturntype()无法检索泛型类型信息。
以下是最小化版本:

public class Test {
    public static void main(String[] args) {
        Method method = B.class.getMethods()[0]; // foo() method inherited from A
        System.out.println(method.getGenericReturnType());
    }

    static class A {
        public List<String> foo() { return null; }
    }

    public static class B extends A {}
}

输出为

java.util.List

没有任何泛型类型信息。我觉得这很奇怪。
然而,改变 A 对…的可见性 public 它正确地给出了

java.util.List<java.lang.String>

问题

我不知道这是一个错误或实际预期的行为。如果这是意料之中的,那么背后的原因是什么?
我正在使用openjdk中的openjdk 15:

// javac
javac 15.0.1

// java
openjdk version "15.0.1" 2020-10-20
OpenJDK Runtime Environment AdoptOpenJDK (build 15.0.1+9)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 15.0.1+9, mixed mode, sharing)

一个朋友也可以复制它:
jdk祖鲁8
采用OpenJDK 10、11、14

调查结果

我做了很多实验,发现 A 以及 B 引发这个问题的原因是 Bpublic 以及 A 不是 public . 任何其他组合和它的工作预期再次。仅此而已
b public ,一个 protected b public ,一个 package-visible b public ,一个 private 表现出怪异的行为。
我尝试在不同的文件中移动代码,将其放入不同的包中,添加或删除 static 到处都是,什么都没变。
我还检查了该方法的源代码

public Type getGenericReturnType() {
  if (getGenericSignature() != null) {
    return getGenericInfo().getReturnType();
  } else { return getReturnType();}
}

哪里 getGenericSignature() 依赖于 String signature 这是在工程建设期间设置的 Method 示例。看来是的 null 出于某种原因在上述情况下。

更新1

我刚刚检查了类的字节码,在 B.class :

public Test$B();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method Test$A."<init>":()V
         4: return
      LineNumberTable:
        line 16: 0

  public java.util.List foo();
    descriptor: ()Ljava/util/List;
    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #7                  // Method Test$A.foo:()Ljava/util/List;
         4: areturn
      LineNumberTable:
        line 16: 0

对我来说这看起来像 B ,出于某种原因,创建了另一个方法 foo() 只需将方法调用转发给 A s foo() 因此没有泛型类型信息。
而且,打电话的时候 B.getDeclaredMethods() 它实际上返回一个方法,即

public java.util.List Test$B.foo()

即使此方法应该排除继承的方法(从文档中):
返回一个数组,其中包含反映该类对象所表示的类或接口的所有声明方法的方法对象,包括public、protected、default(package)access和private方法,但不包括继承的方法。
如果 B 确实创建了一个 Package 器方法。
但是,为什么要创建这样一个方法呢?是否有一个jls部分可以解释这种行为?

f4t66c6m

f4t66c6m1#

当你申报时 A 公众, B.class.getMethods()[0] 不是引用 B ; 它是引用 A.foo() ,其中由于存在 signature .

声明 A 非公有制力量 B.class.getMethods()[0] 参考 B.foo() .
由于没有继承方法的声明,因此无法从对的调用中获取类型 getGenericReturnType 因为类型擦除应用于泛型。
在编译时: List<String> foo() 变成 List foo() .
这就是所有的信息 B 由于签名在 B.foo() 刚刚被移除。

两者 A 以及 B 保持相同的返回类型 foo() : java.util.List (无参数类型)
这是 A.foo() 的声明返回类型。与相同 B.foo() :

不同的是 A 具有有效的签名,因此它将完成对的调用的结果 getGenericReturnType() 通过添加参数类型。
如果是 B ,它只显示它所知道的:只是返回类型。
为了解决可见性难题,我头疼得厉害。要完整解释为什么会发生这种情况,请查阅尤金的答案。说真的,你可以从这类人身上学到很多。

cotxawn7

cotxawn72#

让我们慢慢来。首先,这就是为什么要首先生成一个bridge方法。即使放弃泛型,仍然会有桥接方法。也就是说,这个代码:

static class A {
    public String foo() { return null; }
}

public static class B extends A {}

仍将生成 foo 方法 ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC . 您可以阅读错误描述并理解为什么需要这样做。
另一方面,如果你
A public ,这样的方法不会生成,原因应该很明显,考虑到前面的bug解释(希望如此)。所以我们的想法是如果你有一个非公共课程, javac 将为上述场景生成桥接方法。
现在,如果你把泛型加入到合成方法的组合中,事情就会开始变得明朗起来。例如,您有:

interface WithGeneric<T> {
    public WithGeneric<T> self(T s);
}

public class Impl implements WithGeneric<String> {

    @Override
    public WithGeneric<String> self(String s) {
        return null;
    }
}

在中也会生成一个桥接方法 Impl.class ,但它的声明将是删除接口。换言之,在这个过程中将有两种方法 Impl.class :

public WithGeneric<String> self(String) {...}

public WithGeneric self(Object) {...}

如果你把这两样东西粘在一起:
对于非公共类,将创建一个桥方法(以便反射可以工作)
对于泛型,将创建一个擦除的桥方法(以便擦除的调用可以工作)
一切都会有意义的。

相关问题