有没有办法判断运行时类型是否被删除
Is there a way to tell if the runtime type was erased
这个解释起来有点复杂,但我会尽力的。
假设你有一个通用的 class:
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);
}
}
这样做的是
创建一个 MethodHandle
到 getValue
方法
适应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
的erased类型是Number
,所以调用真的应该是:
result("getValue", Number.class, box); // not Long.class
这对我来说是显而易见的,但对我工作地点的图书馆来访者来说却不是,我不能责怪他们。请注意,这是一个简化的示例...
当他们使用 Long
类型构建 Box<Long> box = new Box<>();
时,进一步提供 Long.class
而不是 Number.class
是很自然的。解决方案显然是微不足道的,但是,我在想,如果我可以(在运行时)"see" getValue
的 return 类型是泛型类型,我可以抛出一条正确的错误消息.例如:
"you provided Long.class, but the generic type was erased to ..."
换句话说,如果我能在运行时告诉 return 类型是 Number.class
来自 getValue
和 的结果一些擦除,我可以在以后的决定中更聪明一点。
这可能吗?
好吧,也许你可以使用旧反射。使用反射允许您按名称和参数类型而不是 return 类型查找方法。然后您可以检查 return 类型以查看调用者是否提供了正确的类型:
Method method = instance.getClass().getMethod(methodName);
Class<?> rtype = method.getReturnType();
if (rtype != propertyClass) {
throw new IllegalArgumentException("must use " + rtype + " instead of " + propertyClass);
}
MethodHandle handle = LOOKUP.unreflect(method);
您可能需要根据需要调整反射查找(getMethod 或 getDeclaredMethod)的方式。您可能还需要检查以确保匹配的方法不是抽象的或静态的。 (有人会认为它不能是抽象的,因为你提供了一个实例,但可能存在我没有想到的边缘情况,例如单独的编译。)你可能还需要检查该方法是否声明在与您正在反思的 class 相同。由于您关心性能,因此进行反射可能会太慢。但是,如果您只关心诊断,则可以尝试快乐的路径,如果您获得 NSME,请进行反射查找以获得正确的 return 类型。
这个解释起来有点复杂,但我会尽力的。
假设你有一个通用的 class:
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);
}
}
这样做的是
创建一个
MethodHandle
到getValue
方法适应
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
的erased类型是Number
,所以调用真的应该是:
result("getValue", Number.class, box); // not Long.class
这对我来说是显而易见的,但对我工作地点的图书馆来访者来说却不是,我不能责怪他们。请注意,这是一个简化的示例...
当他们使用 Long
类型构建 Box<Long> box = new Box<>();
时,进一步提供 Long.class
而不是 Number.class
是很自然的。解决方案显然是微不足道的,但是,我在想,如果我可以(在运行时)"see" getValue
的 return 类型是泛型类型,我可以抛出一条正确的错误消息.例如:
"you provided Long.class, but the generic type was erased to ..."
换句话说,如果我能在运行时告诉 return 类型是 Number.class
来自 getValue
和 的结果一些擦除,我可以在以后的决定中更聪明一点。
这可能吗?
好吧,也许你可以使用旧反射。使用反射允许您按名称和参数类型而不是 return 类型查找方法。然后您可以检查 return 类型以查看调用者是否提供了正确的类型:
Method method = instance.getClass().getMethod(methodName);
Class<?> rtype = method.getReturnType();
if (rtype != propertyClass) {
throw new IllegalArgumentException("must use " + rtype + " instead of " + propertyClass);
}
MethodHandle handle = LOOKUP.unreflect(method);
您可能需要根据需要调整反射查找(getMethod 或 getDeclaredMethod)的方式。您可能还需要检查以确保匹配的方法不是抽象的或静态的。 (有人会认为它不能是抽象的,因为你提供了一个实例,但可能存在我没有想到的边缘情况,例如单独的编译。)你可能还需要检查该方法是否声明在与您正在反思的 class 相同。由于您关心性能,因此进行反射可能会太慢。但是,如果您只关心诊断,则可以尝试快乐的路径,如果您获得 NSME,请进行反射查找以获得正确的 return 类型。