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
方法的通配符返回类型,它目前被调用的是Class<capture of ?>, capture of ?
。我可以将
doSomething
更改为doSomething(Class<?> aClass, Object anInstance)
,但这样会失去类型安全性,并且这不一定是调用该方法的唯一位置。我的问题是:为什么编译器不能推断它们具有相同的基础类型
U
,考虑到显式转换?
编辑(2021年3月9日):
我自行反编译了字节码,以查看为什么rzwitserloot的助手方法确实解决了类型问题。由于类型擦除,它们似乎是相同的调用。我猜编译器只是不够聪明,在转换后无法推断它们是相同的捕获类型,需要类型绑定来帮助。
我已添加以下函数
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
返回一个Class<?>
。因为它是通配符类型,所以从编译器的角度来看,该方法无法保证后续方法调用返回具有相同捕获类型的类。
invokeMethod
和resolveMethod
的<T>
类型参数是无意义的。你可以将方法的参数类型更改为Class<?>
并删除类型参数,而不改变逻辑。 此外,您只需使用resolveMethod(aClass). ifPresent(myMethod-> helper(myMethod.getReturnType(), myMethod.invoke(null))
,而不是isPresent()
和get()
及其他中间步骤。 - Holger