I'm struggling with a simple, fundamental Java language question that isn't making sense. I'm passing an Object[] to a method which expects varargs Object..., but the method seems to be interpreting the request as new Object[]{Object[]}. That is, instead of considering the Object[] as equivalent to Object..., it seems to be passing the Object[] as the first element of the varargs, thus wrapping the Object[] in another array of objects.
I have the following in Java 17:
FooImpl foo = goGetFooImpl();
List<?> methodArgs = List.of(); //no method args
Object[] invokeArgs = Stream.concat(Stream.of(foo, methodArgs.stream()).toArray();
// this gives me the equivalent of `new Object[]{foo}`
// logging `List.of(invokeArgs)` shows `[com.example.FooImpl@…]`
MethodHandle barMethodHandle = goLookUpMethodHandle(foo, "bar");
methodHandle.invoke(invokeArgs); //expects varargs `foo, arg1, arg2, …`
I get:
java.lang.ClassCastException: Cannot cast [Ljava.lang.Object; to com.example.FooImpl
The method signature I'm calling is MethodHandle.invoke(Object... args). It expects varargs, with the first object referencing the target object on which the method is being invoked. So this works:
FooImpl foo = goGetFooImpl();
MethodHandle barMethodHandle = goLookUpMethodHandle(foo, "bar");
barMethodHandle.invoke(foo);
In this case I don't know ahead of time that there are no further arguments (i.e. methodArgs above might not be empty), which is why I combined everything into an Object[]. But even manually testing like this doesn't work:
FooImpl foo = goGetFooImpl();
MethodHandle barMethodHandle = goLookUpMethodHandle(foo, "bar");
barMethodHandle.invoke(new Object[]{foo});
The reason this is confusing me is that this is such a basic Java issue (a newbie question, really), and I have understood for years that Java would by default consider passing Object[] to a method expecting Object... to be equivalent. I had believed that I would have to cast Object[] to Object using (Object)invokeArgs in order for it to be considered the first object of the Object... (which I don't want). But this seems to be happening anyway without the cast.
The error message seems to be telling me that Java is considering Object[] to be the first Object of the Object... varargs, because the method seems to be trying to convert the invokeArgs itself (which is a Object[]) to a reference to FooImpl, which is what is requested as the first argument in the varargs.
This doesn't make sense to me. Either I've misunderstood for years how varargs works, or something weird is going on with the MethodHandle.invoke(…) method specifically, or I'm just having a mental blank.
Even if MethodHandle.invoke(…) has some sort of special behavior, aren't the arguments determined at compile time? How could MethodHandle.invoke(…) have different behavior based upon whether I use invoke(foo) or invoke(new Object[]{foo})? During compilation aren't they equivalent, and in either case the compiler will wind up passing new Object[]{foo} to the method, even in the case of invoke(foo)?
Yes! The signature of
MethodHandle::invokeis a lie. It is not aObject...varargs method, and it does not returnObjecteither. Both the formal parameters and the return type are placeholders. This is becauseMethodHandle::invokeis a signature polymorphic method.See section 15.12.3 of the Java language spec (se20). Quoted here, I've omitted the irrelevant parts:
This affects how the he compile-time parameter types and compile-time result are determined:
(See also the section of the
MethodHandlejavadoc on signature polymorphism)In your case the type of the single actual argument expression is
Object[], so the type of the invocation is(Object[])void.The invocation then fails because the implementation of
invoketries to automatically adapt the argument type to the parameter type of the method handle, which iscom.example.FooImpl. For reference types this is done using a cast, as explained in the documentation ofMethodHandle::asType. SinceObject[]can not be cast toFooImpl, you get theClassCastException.As @Louis Wasserman says in the comments: if you want the
Object[]to be expanded into a list of arguments you need to useMethodHandle::invokeWithArguments.