Java 反射转换方法 ReturnType

Java Reflection Casting Method ReturnType

为了提供一些背景知识,我正在创建一个小的依赖注入器和 运行 解决将方法调用转换回它们的 return 类型的问题。一个最小的例子是:

public class MinimalExample {
    public static <T> void invokeMethod(Class<T> aClass) throws ReflectiveOperationException {
        Optional<Method> myOptMethod = resolveMethod(aClass);
        if (myOptMethod.isPresent()) {
            Method myMethod = myOptMethod.get();
            Object myInstance = myMethod.invoke(myMethod);
            doSomething(myMethod.getReturnType(), myMethod.getReturnType().cast(myInstance));
        }
    }

    private static <T> Optional<Method> resolveMethod(Class<T> aClass) {
        return Stream.of(aClass.getMethods())
                .filter(aMethod -> Modifier.isStatic(aMethod.getModifiers()))
                .filter(aMethod -> aMethod.getParameterCount() == 0)
                .findAny();
    }

    private static <U> void doSomething(Class<U> aClass, U anInstance) {
        // E.g. Map aClass to anInstance.
    }
}

这里的问题是 doSomething 需要用 Class<U>, U 调用,但由于 invoke 方法的通配符 return类型。

我可以将 doSomething 更改为 doSomething(Class<?> aClass, Object anInstance) 但是我失去了类型安全并且这不一定是调用该方法的唯一地方。

我的问题是:为什么编译器不能推断出它们具有相同的基础类型,U,给定显式转换?


编辑(2021 年 3 月 9 日):

我通过反编译字节码的自由来查看为什么 确实解决了类型问题。由于类型擦除,它们似乎是相同的调用。我猜想编译器不够聪明,无法在转换后推断它们是相同的捕获类型,需要类型绑定来帮助。

我添加了以下功能

private static <U> void doSomethingWithTypeBinding(Class<U> aClass, Object anObject) {
    doSomething(aClass, aClass.cast(anObject));
}

private static void doSomethingUnsafe(Class<?> aClass, Object anInstance) {}

我现在分别从第 15 行和第 16 行调用

doSomethingWithTypeBinding(myMethod.getReturnType(), myInstance);
doSomethingUnsafe(myMethod.getReturnType(), myMethod.getReturnType().cast(myInstance));

产生以下字节码:

L5
    LINENUMBER 15 L5
    ALOAD 2
    INVOKEVIRTUAL java/lang/reflect/Method.getReturnType ()Ljava/lang/Class;
    ALOAD 3
    INVOKESTATIC depinjection/handspun/services/MinimalExample.doSomethingWithTypeBinding (Ljava/lang/Class;Ljava/lang/Object;)V
L6
    LINENUMBER 16 L6
    ALOAD 2
    INVOKEVIRTUAL java/lang/reflect/Method.getReturnType ()Ljava/lang/Class;
    ALOAD 2
    INVOKEVIRTUAL java/lang/reflect/Method.getReturnType ()Ljava/lang/Class;
    ALOAD 3
    INVOKEVIRTUAL java/lang/Class.cast (Ljava/lang/Object;)Ljava/lang/Object;
    INVOKESTATIC depinjection/handspun/services/MinimalExample.doSomethingUnsafe (Ljava/lang/Class;Ljava/lang/Object;)V

// access flags 0xA
// signature <U:Ljava/lang/Object;>(Ljava/lang/Class<TU;>;TU;)V
// declaration: void doSomething<U>(java.lang.Class<U>, U)
private static doSomething(Ljava/lang/Class;Ljava/lang/Object;)V
    L0
        LINENUMBER 30 L0
        RETURN
    L1
        LOCALVARIABLE aClass Ljava/lang/Class; L0 L1 0
        // signature Ljava/lang/Class<TU;>;
        // declaration: aClass extends java.lang.Class<U>
        LOCALVARIABLE anInstance Ljava/lang/Object; L0 L1 1
        // signature TU;
        // declaration: anInstance extends U
        MAXSTACK = 0
        MAXLOCALS = 2

// access flags 0xA
// signature <U:Ljava/lang/Object;>(Ljava/lang/Class<TU;>;Ljava/lang/Object;)V
// declaration: void doSomethingWithTypeBinding<U>(java.lang.Class<U>, java.lang.Object)
private static doSomethingWithTypeBinding(Ljava/lang/Class;Ljava/lang/Object;)V
    L0
        LINENUMBER 33 L0
        ALOAD 0
        ALOAD 0
        ALOAD 1
        INVOKEVIRTUAL java/lang/Class.cast (Ljava/lang/Object;)Ljava/lang/Object;
        INVOKESTATIC depinjection/handspun/services/MinimalExample.doSomething (Ljava/lang/Class;Ljava/lang/Object;)V
    L1
        LINENUMBER 34 L1
        RETURN
    L2
        LOCALVARIABLE aClass Ljava/lang/Class; L0 L2 0
        // signature Ljava/lang/Class<TU;>;
        // declaration: aClass extends java.lang.Class<U>
        LOCALVARIABLE anObject Ljava/lang/Object; L0 L2 1
        MAXSTACK = 3
        MAXLOCALS = 2

// access flags 0xA
// signature (Ljava/lang/Class<*>;Ljava/lang/Object;)V
// declaration: void doSomethingUnsafe(java.lang.Class<?>, java.lang.Object)
private static doSomethingUnsafe(Ljava/lang/Class;Ljava/lang/Object;)V
    L0
        LINENUMBER 37 L0
        RETURN
    L1
        LOCALVARIABLE aClass Ljava/lang/Class; L0 L1 0
        // signature Ljava/lang/Class<*>;
        // declaration: aClass extends java.lang.Class<?>
        LOCALVARIABLE anInstance Ljava/lang/Object; L0 L1 1
        MAXSTACK = 0
        MAXLOCALS = 2

我们可以看到 INVOKEVIRTUAL 转换为 INVOKESTATIC 由于它们的运行时类型擦除看起来完全相同。


编辑(2021 年 3 月 12 日):

@Holger , Method#getReturnType return 一个 Class<?>。因为它是通配符,所以从编译器的角度来看,该方法无法保证后续方法调用 returning a Class 具有相同的捕获类型。

首先调用是:

Object myInstance = myMethod.invoke(null);

myMethodstatic(正如您已经在 resolveMethod 中找到的那样),因此您需要传递一个 null,否则您将需要一个实例,你没有。

然后修复您的示例,相当简单:

Method myMethod = myOptMethod.get();
Object myInstance = myMethod.invoke(null);

Class<?> cls = myMethod.getReturnType();
Object obj = myMethod.getReturnType().cast(myInstance);

doSomething(cls, obj);

该方法将定义更改为:

 private static <U> void doSomething(Class<? extends U> aClass, U anInstance) {....}

类型变量是编译器想象的产物:它们在编译后无法存活(擦除*)。最好将它们视为 linking things。仅在一个地方使用的类型变量永远是完全无用的;一旦它们出现在两个地方,这就很有用了:它可以让您 link 一起使用多种类型,表示出现次数相同。例如,您可以将 .add(Obj thingToAdd) 的参数类型和 .get(int idx) 的 return 类型绑定在一起以获得 java.util.List.

在这里,您想要 link myMethod.getReturnTypeClass<X>myInstance 变量。正如您所意识到的,这是不可能的,因为编译器 不知道 它们最终将成为同一类型。但是,通过调用 Class<X>cast() 方法,我们解决了该部分问题。

但是您仍然需要某种类型的变量作为将事物联系在一起的工具,而您没有。 ? 类似于一次性使用的类型变量; Class<?> cls 和 myMethod.getReturnType().cast(myInstance)` 是 'different'吨。你需要一个类型变量。当然可以介绍一个:

private static <X> helper(Class<X> x, Object myInstance) {
    doSomething(x, x.cast(myInstance));
}

将此方法添加到您的代码中并调用此方法,而不是调用 doSomething。我们在此处创建的 <X> 用于将结果联系在一起。

*) 它们保留在 public 签名中,当然,但在其他任何地方,在运行时它们都被删除了。

您可以在此处使用另一个选项:doSomething 方法是 private,因此您可以完全控制它。因此,您可以只在其中移动演员表,这可以解决所有问题,或者,您可以这样写:

/** precondition: o must be an instance of c */
private static void doSomething(Class<?> c, Object o) {
}

因为是私有方法,引入前置条件就好了。您可以完全控制调用此方法的所有代码。如果你真的想要,你可以添加一个运行时检查(if (!c.isInstanceof(o)) throw new IllegalArgumentException("o not instance of c"); 在顶部),但是否值得这样做是 java 生态系统中私有方法的公开辩论。通常的结论是不要这样做,或者为此使用 assert 关键字。

注意:这有一些残暴的 null/optional 处理。如果找不到要解决的方法,你..只是默默地什么都不做?这就是 NPE 更好的原因:至少粗心的编码会导致异常而不是徒劳。