是否有办法判断是否删除了运行时类型

问题描述 投票:3回答:2

这将很难解释,但我会尝试。

假设您有一个通用类:

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);
    }
}

这是什么

  • 创建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);
}

这应该起作用,对吗?好吧,不。这将失败:

 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

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


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

"you provided Long.class, but the generic type was erased to ..."

换句话说,如果我可以在运行时告诉返回类型是Number.classgetValue 它是某些[[erasure的结果,那么我以后可能会更聪明决定。

有可能吗?
java reflection java-8 java-11 methodhandle
2个回答
0
投票
一个相当丑陋的选择(但至少是一个选择)是,查找类的所有方法并查看是否适合。

private static Class<?> findReturnTypeOfCompatibleMethod(String name, Class<?> base, Class<?> propertyClass) { Method[] methods = base.getMethods(); for (Method method : methods) { if (method.getReturnType().isAssignableFrom(propertyClass)) { // Types are at least compatible, so most likely the erasure or a bridge method return method.getReturnType(); } } return null; }

您可以使用它来打印错误消息,或使用找到的类型来解析MethodHandle

MethodHandle handle; try { handle = LOOKUP.findVirtual(instance.getClass(), methodName, MethodType.methodType(propertyClass)); } catch (NoSuchMethodException notFound) { Class<?> resolved = findReturnTypeOfCompatibleMethod(methodName, instance.getClass(), propertyClass); if (resolved == null) { throw notFound; } System.err .println("you provided " + propertyClass + ", but the generic type was erased to " + resolved); handle = LOOKUP.findVirtual(instance.getClass(), methodName, MethodType.methodType(resolved)); }


如果警告/错误消息是您想要的“全部”,则可以查看method.getGenericReturnType()并“分析” TypeVariable<?>

private static Class<?> isBoundGeneric(String name, Class<?> base, Class<?> propertyClass) { Method[] methods = base.getMethods(); for (Method method : methods) { Type genericReturnType = method.getGenericReturnType(); if (genericReturnType instanceof TypeVariable<?>) { Type[] bounds = ((TypeVariable<?>) genericReturnType).getBounds(); System.out.println(((TypeVariable<?>) genericReturnType).getName()); System.out.println("Is typevariable with bounds: " + Arrays.toString(bounds)); } } return null; }


0
投票
嗯,也许您可​​以使用良好的旧反射。使用反射允许您按名称和参数类型查找方法,但不能按返回类型查找。然后,您可以检查返回类型,以查看调用方是否提供了正确的返回类型:

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)的方式。您可能还需要检查以确保匹配的方法不是抽象的或静态的。 (一个人会认为它不可能是抽象的,因为为您提供了一个实例,但是可能有一个我没有想到的极端情况,例如单独的编译。)并且您可能还需要检查方法是否在上声明您正在考虑的同一个班级。由于您担心性能,因此进行反射可能会太慢。但是,如果您所关心的只是诊断,则可以尝试轻松的方法,如果您获得了NSME,则可以进行反射式查找以获取正确的返回类型。    
© www.soinside.com 2019 - 2024. All rights reserved.