簡體   English   中英

反射 - Method::getGenericReturnType 沒有泛型 - 可見性

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

描述

我有一個奇怪的問題,即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 {}
}

Output 是

java.util.List

沒有任何通用類型信息。 這對我來說似乎很奇怪。

但是,將A的可見性更改為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

發現

我做了很多實驗,發現AB之間唯一觸發此問題的可見性組合是BpublicA不是public 任何其他組合,它都會再次按預期工作。 所以只有

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

表現出奇怪的行為。

我嘗試在不同的文件中移動代碼,將其放入不同的包中,在這里和那里添加或刪除static並且沒有任何改變。

我還檢查了該方法的源代碼,即

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

其中getGenericSignature()依賴於在構造Method實例期間設置的String signature 在上述情況下,由於某種原因,它似乎是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()

即使此方法應該排除繼承的方法(來自文檔):

返回包含 Method 對象的數組,該對象反映 class 或由此 Class object 表示的接口的所有聲明方法,包括公共、受保護、默認(包)訪問和私有方法,但不包括繼承方法

如果B真的創建了一個包裝器方法,那么現在這將是有意義的。

但是,它為什么要創建這樣的方法? 是否有解釋此行為的 JLS 部分?

讓我們在這里慢慢來。 首先,這就是為什么要生成橋接方法的原因。 即使你放棄generics,仍然會有橋接方法。 也就是說,這段代碼:

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

public static class B extends A {}

仍將生成帶有ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETICfoo方法。 您可以閱讀錯誤描述並了解為什么需要這樣做。

另一方面,如果您將A public ,則不會生成這樣的方法,原因應該很明顯,考慮到前面的錯誤解釋(我希望)。 所以想法是,如果你有一個非公共的class, javac將為上面的場景生成一個橋接方法。


現在,如果您將 generics 添加到合成方法的組合中,事情就會開始顯現出來。 例如,你有這個:

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

如果你把這兩件事粘合起來:

  • 在非公共類的情況下,將創建一個橋接方法(以便反射起作用)

  • 在 generics 的情況下,將創建一個擦除的橋接方法(以便擦除的調用可以工作)

事情會(以某種方式)有意義。

當您聲明A public 時, B.class.getMethods()[0]沒有引用B 它引用A.foo() ,其中聲明了方法並獲得了類型,因為存在signature

在此處輸入圖像描述


聲明A非公開強制B.class.getMethods()[0]參考B.foo()

由於沒有聲明繼承方法,因此無法通過對getGenericReturnType的調用獲取類型,因為類型擦除應用於 generics。

在編譯時:

List<String> foo()變為List foo()

這就是B可以提供給您的有關方法返回類型的所有信息,因為剛剛刪除了B.foo()中的簽名。

在此處輸入圖像描述


AB都為foo()持有相同的返回類型java.util.List無參數類型

這是A.foo()聲明的返回類型。 B.foo()相同:

在此處輸入圖像描述

不同之處在於A具有有效的簽名,因此它將通過添加參數類型來完成對getGenericReturnType()的調用的結果。

B的情況下,它只會顯示它所知道的:只是返回類型。


試圖解決能見度難題時,我感到非常頭疼。 有關為什么會發生這種情況的完整解釋,請查看 Eugene 的答案。 說真的,你可以從這種人身上學到很多東西。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM