説明
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
ジェネリック型情報なし。これは私には奇妙に思えます。
ただし、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 を採用
所見
私は多くの実験を行い、この問題を引き起こすAとの間の唯一の可視性の組み合わせは when isとis notであることがわかりました。他の組み合わせでも、期待どおりに機能します。だからだけBBpublicA public
- B
public、Aprotected - B
public、Apackage-visible - B
public、Aprivate
奇妙な行動を示します。
コードを別のファイルに移動したり、別のパッケージに入れたり、staticあちこちに追加または削除したりしてみましたが、何も変わりませんでした。
メソッドのソースコードも確認しました。
public Type getGenericReturnType() {
if (getGenericSignature() != null) {
return getGenericInfo().getReturnType();
} else { return getReturnType();}
}
whereは、インスタンスの構築中に設定さgetGenericSignature()れる a に依存します。何らかの理由で上記の状況になっているようです。String signatureMethodnull
更新 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()
このメソッドは継承されたメソッドを除外することになっていますが(ドキュメントから):
この Class オブジェクトによって表されるクラスまたはインターフェイスのすべての宣言されたメソッドを反映する Method オブジェクトを含む配列を返します。これには、パブリック、プロテクト、デフォルト (パッケージ) アクセス、およびプライベート メソッドが含まれますが、継承されたメソッドは除外されます。
B本当にラッパーメソッドを作成した場合、これは理にかなっています。
しかし、なぜそのようなメソッドを作成しているのでしょうか? この動作を説明する JLS セクションはありますか?