Zab*_*uza 7 java generics reflection javac java-bridge-method
我有一个奇怪的问题,其中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 {}
}
Run Code Online (Sandbox Code Playgroud)
输出是
java.util.List
Run Code Online (Sandbox Code Playgroud)
没有任何通用类型信息。这对我来说似乎很奇怪。
但是,将As 的可见性更改为public并正确地给出
java.util.List<java.lang.String>
Run Code Online (Sandbox Code Playgroud)
我不知道这是错误还是实际预期的行为。如果是预期的,其背后的原因是什么?
我正在使用来自 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)
Run Code Online (Sandbox Code Playgroud)
朋友也可以复制它:
我进行了大量实验A,B发现触发此问题的和之间的唯一可见性组合是 when Bispublic和Ais not public。任何其他组合,它再次按预期工作。所以只有
public,甲protectedpublic,甲package-visiblepublic,甲private表现出奇怪的行为。
我尝试在不同的文件中移动代码,将其放入不同的包中,static在这里和那里添加或删除,但没有任何改变。
我还检查了该方法的源代码,即
public Type getGenericReturnType() {
if (getGenericSignature() != null) {
return getGenericInfo().getReturnType();
} else { return getReturnType();}
}
Run Code Online (Sandbox Code Playgroud)
wheregetGenericSignature()依赖于String signature在构造Method实例期间设置的a 。null在上述情况下,似乎出于某种原因。
我只是在查看类的字节码,并在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
Run Code Online (Sandbox Code Playgroud)
对我来说B,出于某种原因,这看起来像是创建了另一个方法foo(),该方法只是将方法调用转发到As foo(),因此没有通用类型信息。
而且,在调用B.getDeclaredMethods()它的时候实际上返回了一个方法,即
public java.util.List Test$B.foo()
Run Code Online (Sandbox Code Playgroud)
即使此方法应该排除继承的方法(来自文档):
返回一个包含 Method 对象的数组,该对象反映了此 Class 对象表示的类或接口的所有声明方法,包括公共、受保护、默认(包)访问和私有方法,但不包括继承的方法。
如果B真的创建了一个包装方法,现在就有意义了。
但是,它为什么要创建这样的方法?是否有解释这种行为的 JLS 部分?
让我们在这里慢慢来。首先,这就是为什么要开始生成桥接方法的原因。即使你放弃泛型,仍然会有一个桥接方法。也就是说,这段代码:
static class A {
public String foo() { return null; }
}
public static class B extends A {}
Run Code Online (Sandbox Code Playgroud)
仍然会生成一个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;
}
}
Run Code Online (Sandbox Code Playgroud)
中也会生成一个桥接方法Impl.class,但它的声明将是接口的擦除。换句话说,将有两种方法Impl.class:
public WithGeneric<String> self(String) {...}
public WithGeneric self(Object) {...}
Run Code Online (Sandbox Code Playgroud)
如果你把这两件事粘起来:
在非公共类的情况下,将创建一个桥接方法(以便反射可以工作)
在泛型的情况下,将创建擦除的桥接方法(以便擦除的调用可以工作)
事情会(以某种方式)有意义。
| 归档时间: |
|
| 查看次数: |
191 次 |
| 最近记录: |