简体   繁体   English

反射 - Method::getGenericReturnType 没有泛型 - 可见性

[英]Reflection - Method::getGenericReturnType no generic - visbility

Description描述

I have an odd issue in which Method::getGenericReturnType() is not able to retrieve the generic type information.我有一个奇怪的问题,即Method::getGenericReturnType()无法检索泛型类型信息。

Here is the minimized version:这是最小化版本:

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

Output is Output 是

java.util.List

without any generic type information.没有任何通用类型信息。 This seems odd to me.这对我来说似乎很奇怪。

However, changing A s visibility to public and it correctly gives但是,将A的可见性更改为public并且它正确地给出了

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

Question问题

I do not know if this is a bug or actually expected behavior.我不知道这是一个错误还是实际预期的行为。 If it is expected, what is the reasoning behind it?如果是预期的,其背后的原因是什么?

I am using OpenJDK 15 from AdoptOpenJDK:我正在使用来自 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)

A friend could also reproduce it in:朋友也可以复制它:

  • JDK Zulu 8 JDK祖鲁8
  • AdoptOpenJDK 10, 11, 14采用OpenJDK 10、11、14

Findings发现

I experimented a lot and figured out that the only visibility-combination between A and B that triggers this issue is when B is public and A is not public .我做了很多实验,发现AB之间唯一触发此问题的可见性组合是BpublicA不是public Any other combination and it works as expected again.任何其他组合,它都会再次按预期工作。 So only所以只有

  • B public , A protected B public ,A protected
  • B public , A package-visible B public ,A package-visible
  • B public , A private B public ,A private

show weird behavior.表现出奇怪的行为。

I tried moving the code around in different files, putting it into different packages, adding or removing static here and there and nothing changed.我尝试在不同的文件中移动代码,将其放入不同的包中,在这里和那里添加或删除static并且没有任何改变。

I also checked the source code of the method, which is我还检查了该方法的源代码,即

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

where getGenericSignature() relies on a String signature that is set during construction of the Method instance.其中getGenericSignature()依赖于在构造Method实例期间设置的String signature It appears that it is null for some reason in above situation.在上述情况下,由于某种原因,它似乎是null


Update 1更新 1

I was just checking out the bytecode of the classes and found this in B.class :我刚刚检查了类的字节码,并在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

To me this looks like B , for some reason, created another method foo() that simply forwards the method call to A s foo() and hence has no generic type information.对我来说,这看起来像B ,出于某种原因,创建了另一个方法foo() ,它只是将方法调用转发给A s foo() ,因此没有通用类型信息。

Moreover, when calling B.getDeclaredMethods() it actually returns a method, namely此外,当调用B.getDeclaredMethods()它实际上返回一个方法,即

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

Even though this method is supposed to exclude inherited methods (from the documentation ):即使此方法应该排除继承的方法(来自文档):

Returns an array containing Method objects reflecting all the declared methods of the class or interface represented by this Class object, including public, protected, default (package) access, and private methods, but excluding inherited methods .返回包含 Method 对象的数组,该对象反映 class 或由此 Class object 表示的接口的所有声明方法,包括公共、受保护、默认(包)访问和私有方法,但不包括继承方法

Which would now make sense if B really created a wrapper-method.如果B真的创建了一个包装器方法,那么现在这将是有意义的。

However, why is it creating such a method?但是,它为什么要创建这样的方法? Is there maybe a JLS section that explains this behavior?是否有解释此行为的 JLS 部分?

Let's take it slow here.让我们在这里慢慢来。 First of all, here is why a bridge method is generated to begin with.首先,这就是为什么要生成桥接方法的原因。 Even if you drop generics, there will still be a bridge method.即使你放弃generics,仍然会有桥接方法。 That is, this code:也就是说,这段代码:

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

public static class B extends A {}

will still generate a foo method with ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC .仍将生成带有ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETICfoo方法。 You can read the bug description and understand why that is needed.您可以阅读错误描述并了解为什么需要这样做。

On the other hand if you make A public , such a method will not be generated, the reason should be obvious, considering the previous bug explanation (I hope).另一方面,如果您将A public ,则不会生成这样的方法,原因应该很明显,考虑到前面的错误解释(我希望)。 So the idea is that if you have a non-public class, javac will generate a bridge method for the scenario above.所以想法是,如果你有一个非公共的class, javac将为上面的场景生成一个桥接方法。


Now, if you add generics into that mix of synthetic methods, things start to shed some light.现在,如果您将 generics 添加到合成方法的组合中,事情就会开始显现出来。 For example, you have this:例如,你有这个:

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

public class Impl implements WithGeneric<String> {

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

There will be a bridge method generated too in Impl.class , but its declaration is going to be the erasure of the interface.Impl.class中也会生成一个桥接方法,但它的声明将是接口的擦除。 In other words there will be two methods in Impl.class :换句话说,在Impl.class中会有两种方法:

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

public WithGeneric self(Object) {...}

If you glue these two things:如果你把这两件事粘合起来:

  • in case of non-public classes a bridge method will be created (so that reflection would work)在非公共类的情况下,将创建一个桥接方法(以便反射起作用)

  • in case of generics an erased bridge method will be created (so that erased calls would work)在 generics 的情况下,将创建一个擦除的桥接方法(以便擦除的调用可以工作)

things will make (somehow) sense.事情会(以某种方式)有意义。

When you declare A public, B.class.getMethods()[0] is not referencing B ;当您声明A public 时, B.class.getMethods()[0]没有引用B It is referencing A.foo() , where the method is declared and the type is obtained because of the existance of the signature .它引用A.foo() ,其中声明了方法并获得了类型,因为存在signature

在此处输入图像描述


Declaring A non-public forces B.class.getMethods()[0] reference B.foo() .声明A非公开强制B.class.getMethods()[0]参考B.foo()

As there is no declaration of the inherited method, the type can't be obtained from the call to getGenericReturnType due to type erasure being applied to generics.由于没有声明继承方法,因此无法通过对getGenericReturnType的调用获取类型,因为类型擦除应用于 generics。

In compile time:在编译时:

List<String> foo() becomes List foo() . List<String> foo()变为List foo()

And that's all the information B could give to you regarding the method's return type, since the signature in B.foo() was just removed.这就是B可以提供给您的有关方法返回类型的所有信息,因为刚刚删除了B.foo()中的签名。

在此处输入图像描述


Both A and B hold the same return type for foo() : java.util.List ( without parameter type ) AB都为foo()持有相同的返回类型java.util.List无参数类型

This is A.foo() 's declared return type.这是A.foo()声明的返回类型。 The same than B.foo() :B.foo()相同:

在此处输入图像描述

The difference is that A has a valid signature, so it will complete the result from the call to getGenericReturnType() by adding the parameter types.不同之处在于A具有有效的签名,因此它将通过添加参数类型来完成对getGenericReturnType()的调用的结果。

In the case of B , it will only show what it knows: just the return type.B的情况下,它只会显示它所知道的:只是返回类型。


I suffered strong headaches trying to solve the visibility puzzle.试图解决能见度难题时,我感到非常头疼。 For a complete explanation of why this happens, look up at Eugene's answer.有关为什么会发生这种情况的完整解释,请查看 Eugene 的答案。 Seriously, you can learn a lot from this type of guy.说真的,你可以从这种人身上学到很多东西。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM