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)
?
or something weird is going on with the MethodHandle.invoke(…) method specifically
Yes! The signature of MethodHandle::invoke
is a lie. It is not a Object...
varargs method, and it does not return Object
either. Both the formal parameters and the return type are placeholders. This is because MethodHandle::invoke
is a signature polymorphic method.
See section 15.12.3 of the Java language spec (se20). Quoted here, I've omitted the irrelevant parts:
A method is signature polymorphic if all of the following are true:
It is declared in the
java.lang.invoke.MethodHandle
class or thejava.lang.invoke.VarHandle
class.It has a single variable arity parameter (§8.4.1) whose declared type is
Object[]
.It is
native
.
This affects how the he compile-time parameter types and compile-time result are determined:
If the compile-time declaration for the method invocation is a signature polymorphic method, then:
The compile-time parameter types are the types of the actual argument expressions. ...
The compile-time result is determined as follows:
...
- Otherwise, if the method invocation expression is an expression statement, the compile-time result is
void
.
(See also the section of the MethodHandle
javadoc 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 invoke
tries to automatically adapt the argument type to the parameter type of the method handle, which is com.example.FooImpl
. For reference types this is done using a cast, as explained in the documentation of MethodHandle::asType
. Since Object[]
can not be cast to FooImpl
, you get the ClassCastException
.
As @Louis Wasserman says in the comments: if you want the Object[]
to be expanded into a list of arguments you need to use MethodHandle::invokeWithArguments
.