Java 反射转换方法 ReturnType

问题描述

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

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 调用,但由于 Class<capture of ?>,capture of ? 方法通配符返回类型,它当前正在用 invoke 调用

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

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


编辑(2021 年 3 月 9 日):

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

添加了以下功能

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 pointed out in the commentsMethod#getReturnType 返回 Class<?>。因为它是通配符,所以从编译器的角度来看,该方法不能保证后续方法调用返回具有相同捕获类型的类。

解决方法

类型变量是编译器想象的虚构:它们在编译(擦除*)后无法生存。最好将它们视为链接事物。一个只在一个地方使用的类型变量是完全没有用的;一旦它们出现在两个地方,现在这很有用:它允许您将类型的多个用法链接在一起,也就是说出现是相同的。例如,您可以将 .add(Obj thingToAdd) 的参数类型和 .get(int idx) 的返回类型联系在一起以得到 java.util.List

在这里,您希望将 Class<X>myMethod.getReturnTypemyInstance 变量链接在一起。正如您所意识到的,这是不可能的,因为编译器不知道它们最终会是相同的类型。但是,通过调用 cast()Class<X> 方法,我们可以解决该部分问题。

但是您仍然需要某种类型变量来充当将事物联系在一起的工具,而您没有。 ? 类似于 one-use-and-done 类型变量; Class<?> cls 和 myMethod.getReturnType().cast(myInstance)` 是“不同的”?你需要一个类型变量。你当然可以介绍一个:

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

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

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

此处提供了另一种选择:doSomething 方法是私有,因此您可以完全控制它。因此,您可以直接将演员表移到其中来解决所有问题,或者,您可以这样编写:

/** 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 关键字。

注意:这有一些糟糕的空/可选处理。万一找不到要解决的方法你..就默默的什么都不做?这就是 NPE 更好的原因:至少那样粗心的编码会导致异常而不是疯狂的追逐。

,

首先调用的是:

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) {....}