7

描述

我有一个奇怪的问题,即Method::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

没有任何通用类型信息。这对我来说似乎很奇怪。

但是,将As 可见性更改为public并正确给出

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

问题

我不知道这是一个错误还是实际预期的行为。如果是预期的,其背后的原因是什么?

我正在使用来自 AdoptOpenJDK 的 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触发了这个问题是 when BispublicAis not public。任何其他组合,它都会再次按预期工作。所以只有

  • public,甲protected
  • public,甲package-visible
  • public,甲private

表现出奇怪的行为。

我尝试在不同的文件中移动代码,将其放入不同的包中,static在这里和那里添加或删除,没有任何改变。

我还检查了该方法的源代码,即

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

wheregetGenericSignature()依赖于在实例String signature构造过程中设置的a。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(),该方法只是将方法调用转发给As foo(),因此没有泛型类型信息。

而且,调用B.getDeclaredMethods()它实际上返回一个方法,即

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

即使此方法应该排除继承的方法(来自文档):

返回一个包含 Method 对象的数组,该对象反映此 Class 对象表示的类或接口的所有声明方法,包括公共、受保护、默认(包)访问和私有方法,但不包括继承方法

B如果真的创建了一个包装方法,那么现在这将是有意义的。

但是,它为什么要创建这样的方法?是否有解释此行为的 JLS 部分?

4

2 回答 2

7

让我们在这里慢慢来。首先,这就是生成桥接方法的原因。即使你放弃了泛型,仍然会有一个桥接方法。也就是说,这段代码:

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

public static class B extends A {}

仍然会生成一个foo带有ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC. 您可以阅读错误描述并了解为什么需要这样做。

另一方面,如果你 make ,则不会A public生成这样的方法,原因应该很明显,考虑到前面的错误解释(我希望)。所以想法是,如果您有一个非公共类,将为上述场景生成一个桥接方法。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) {...}

如果你把这两件事粘合起来:

  • 在非公共类的情况下,将创建一个桥接方法(以便反射起作用)

  • 在泛型的情况下,将创建一个擦除的桥接方法(这样擦除的调用就可以工作)

事情会(以某种方式)有意义。

于 2021-01-07T18:33:52.973 回答
3

当您声明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()刚刚删除了 in 的签名。

在此处输入图像描述


两者都A持有B相同的返回类型foo():(java.util.List参数类型

这是A.foo()的声明返回类型。相同于B.foo()

在此处输入图像描述

不同之处在于它A具有有效的签名,因此它将getGenericReturnType()通过添加参数类型来完成调用的结果。

在 的情况下B,它只会显示它所知道的:只是返回类型。


试图解决能见度难题时,我感到非常头疼。有关为什么会发生这种情况的完整解释,请查看 Eugene 的答案。说真的,你可以从这种人身上学到很多东西。

于 2021-01-07T14:11:48.040 回答