有没有办法判断运行时类型是否被擦除

Eug*_*ene 4 java reflection java-8 methodhandle java-11

解释起来会有点复杂,但我会尝试。

假设你有一个泛型类:

static class Box<T extends Number> {

    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }

}
Run Code Online (Sandbox Code Playgroud)

以及一种允许以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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是做什么的

  • MethodHandlegetValue方法创建一个

  • 调整它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);
}
Run Code Online (Sandbox Code Playgroud)

这应该有效,对吧?嗯,不。这将失败:

 Caused by: java.lang.NoSuchMethodException: no such method: GenericTest$Box.getValue()Long/invokeVirtual
Run Code Online (Sandbox Code Playgroud)

我明白为什么,因为擦除的类型T extends NumberNumber,所以调用真的应该是:

result("getValue", Number.class, box); // not Long.class
Run Code Online (Sandbox Code Playgroud)

这对我来说很明显,但对我工作场所图书馆的呼叫者来说不是,我不能责怪他们。请注意,这是一个简化的示例...


当他们Box<Long> box = new Box<>();使用Long类型构建时,提供Long.class进一步的类型是很自然的,而不是Number.class. 解决方案显然是微不足道的,但是,我想如果我可以(在运行时)“看到”返回类型getValue是泛型类型,我可以抛出正确的错误消息。例如:

"you provided Long.class, but the generic type was erased to ..."
Run Code Online (Sandbox Code Playgroud)

换句话说,如果我能在运行时告诉我返回类型是Number.classfromgetValue 并且它是一些擦除的结果,我在以后的决定中可能会更聪明一些。

那可能吗?

Stu*_*rks 5

好吧,也许你可以使用很好的旧反射。使用反射允许您通过名称和参数类型而不是返回类型查找方法。然后,您可以检查返回类型以查看调用者是否提供了正确的类型:

    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);
Run Code Online (Sandbox Code Playgroud)

您可能需要根据需要调整反射查找(getMethod 或 getDeclaredMethod)的方式。您可能还需要检查以确保匹配的方法不是抽象的或静态的。(有人会认为它不能是抽象的,因为您提供了一个实例,但可能存在我没有想到的边缘情况,例如单独编译。)而且您可能还需要检查该方法是否在你正在反思的同一个班级。由于您担心性能,因此进行反射可能太慢了。但是,如果您只关心诊断,则可以尝试快乐的路径,如果您获得 NSME,请进行反射查找以获得正确的返回类型。

  • @Eugene,您可以假设每当 `getGenericReturnType().getClass() != Class.class` 时,就会发生某种泛型。 (3认同)