【发布时间】:2020-10-02 16:26:37
【问题描述】:
这解释起来会有点复杂,但我会尝试的。
假设你有一个泛型类:
static class Box<T extends Number> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
还有一个允许以反射方式调用getValue的方法:
// it's just an example, the real world scenario is slightly more involved
private static final Lookup LOOKUP = MethodHandles.lookup();
public static <T, R> T result(String methodName, Class<T> propertyClass, R instance) {
try {
/* line1 */
MethodHandle handle = LOOKUP.findVirtual(
instance.getClass(),
methodName,
MethodType.methodType(propertyClass)
);
/* line2 */
handle = handle.asType(handle.type()
.changeReturnType(Object.class)
.changeParameterType(0, Object.class));
/* line3 */
Object obj = handle.invokeExact(instance);
return propertyClass.cast(obj);
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
这是做什么的
为
getValue方法创建一个MethodHandle调整
MethodHandle,这样我就可以在上面调用invokeExact(否则我需要调用invoke,这会更慢)。但这一步完全是可选的。一旦我构建了
MethodHandle,就调用它。
现在让我们尝试调用它:
public static void main(String[] args) throws Throwable {
Box<Long> box = new Box<>();
box.setValue(42L);
result("getValue", Long.class, box);
}
这应该可行,对吧?嗯,没有。这将失败:
Caused by: java.lang.NoSuchMethodException: no such method: GenericTest$Box.getValue()Long/invokeVirtual
我明白为什么,因为T extends Number的已擦除类型是Number,所以调用应该是:
result("getValue", Number.class, box); // not Long.class
这对我来说很明显,但对我工作场所图书馆的来电者来说却不是,我不能责怪他们。请注意,这是一个简化的示例...
当他们使用Long 类型构建Box<Long> box = new Box<>(); 时,很自然地进一步提供Long.class,而不是Number.class。解决方案显然是微不足道的,但是,我在想如果我可以(在运行时)“看到”getValue 的返回类型是泛型类型,我可以抛出一个正确的错误消息。例如:
"you provided Long.class, but the generic type was erased to ..."
换句话说,如果我可以在运行时告诉Number.class 来自getValue 并且这是一些擦除的结果,我可以在以后的决定中要聪明一点。
这可能吗?
【问题讨论】:
-
我想你可以尝试使用
isAssignableFrom进行匹配? -
也许这有帮助 - docs.oracle.com/javase/8/docs/api/java/lang/…?您可以调用
instance.getClass().getTypeParameters()并检查它是否具有通用参数。要 100% 正确,您也必须遍历该类的超类型(检查它们是否也有泛型)
标签: java reflection java-8 java-11 methodhandle