To give some background, I am creating a small dependency injector and running into problems casting method invocations back into their return types. A minimal example is:
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.
}
}
The problem here, is that doSomething needs to be called with Class<U>, U, but it is currently being called with Class<capture of ?>, capture of ? due to the invoke method's wildcard return type.
I could change doSomething to doSomething(Class<?> aClass, Object anInstance) but then I lose the type safety and this is not necessarily the only place that method is called.
My question is: Why can the compiler not infer they have the same underlying type, U, given the explicit cast?
Edit (09 Mar 2021):
I went through the liberty of decompiling the byte code in order to see why rzwitserloot's helper method does indeed resolve the type problem. They seem to be identical calls due to type erasure. I guess that the compiler is just not smart enough to infer they are the same capture type after the cast and needs the type-binding to help.
I've added the following functions
private static <U> void doSomethingWithTypeBinding(Class<U> aClass, Object anObject) {
doSomething(aClass, aClass.cast(anObject));
}
private static void doSomethingUnsafe(Class<?> aClass, Object anInstance) {}
which I call from lines 15 and 16 respectively now
doSomethingWithTypeBinding(myMethod.getReturnType(), myInstance);
doSomethingUnsafe(myMethod.getReturnType(), myMethod.getReturnType().cast(myInstance));
Resulting in the following bytecode:
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
We can see the INVOKEVIRTUAL cast right into the INVOKESTATIC look identical due to their runtime type erasure.
Edit (12 Mar 2021):
@Holger pointed out in the comments, Method#getReturnType returns a Class<?>. Because it is wildcarded, the method, from the compiler's point-of-view, has no guarantee of subsequent method calls returning a Class with the same capture type.