调用带有字符串数组参数的方法会导致非法参数异常。

3

我正在使用运行时反射来加载一个包含以下两个方法的类:

public static void foo(int[] args)
{
    System.out.print("foo invoked: ");
    for(int arg : args)
        System.out.print(arg + " ");
    
    System.out.println();
}

public static void bar(String[] args)
{
    System.out.print("bar invoked: ");
    for(String arg : args)
        System.out.print(arg + " ");
    
    System.out.println();
}

(两个方法完全相同,只是一个使用 int 数组,另一个使用字符串数组作为参数)

我尝试像这样调用这两个方法:

    int[] intArr = {1,2,3};
    clazz.getMethod("foo", int[].class).invoke(null, intArr);

    String[] strArr = {"1","2","3"};
    clazz.getMethod("bar", String[].class).invoke(null, strArr); //Exception

(这里的“clazz”是包含这两个方法的类,我已经在运行时加载了它)

第一次调用不会抛出异常并输出预期的输出结果,但第二次调用会抛出以下异常:

Exception in thread "main" java.lang.IllegalArgumentException: wrong number of arguments
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at ReflectionTests.main(ReflectionTests.java:63)

为什么会这样?
2个回答

7

注意invoke的签名:

public Object invoke(Object obj, Object... args)

args 是一个可变参数,允许您将调用方法的每个参数作为 invoke 的参数传递,例如:

someStaticMethod.invoke(null, param1, param2, param3);

将会调用:

someStaticMethod(param1, param2, param3);

另一方面,args 的类型实际上只是 Object[],并且存在从 String[]Object[] 的转换,因为数组是协变的。
因此,当您将 strArrString[] 传递给 invoke 时,可能会发生以下两种情况之一:
- strArr 被转换为 Object[],每个 String 都被视为要调用的方法的参数。 - 整个 strArr 被视为要调用的方法的一个参数,就像非数组类型一样。
编译器恰好偏好第一种情况(请参见此处。注意可变性调用具有最低优先级),因此实际上您正在将参数"1""2""3"传递给该方法,而不是一个单独的 String[]
另一方面,int[] 不能直接转换为 Object[],因为 int 是原始类型,所以编译器只能选择上述第二个选项。
强制选择第二个选项的一种方法是将其转换为 Object
clazz.getMethod("bar", String[].class).invoke(null, (Object)strArr);

另一种方法是创建另一个包装 String[]Object[]
clazz.getMethod("bar", String[].class).invoke(null, new Object [] { strArr });

2
如果有人想知道为什么编译器更喜欢第一种行为,那是因为在varargs引入之前,invoke(null, strArr)是一个有效的调用,因此它的行为不允许改变。相反,在varargs之前,任何不能分配给Object[]的参数都是无效的,因此现在将int[]参数塞入幕后的数组中以将其作为反射方法的单个参数处理时,就没有兼容性问题了。 - Holger

-1

在调用invoke()方法时,你必须将初始化的对象作为第一个参数传递进去,该对象将执行该方法。

Foo foo = new Foo(); Method method = clazz.getMethod("method");

method.invoke(foo, params[ ]);

invoke()需要执行该方法的对象。


1
我不相信在方法是静态的时候你必须这样做。这也表明第一个方法:“foo”运行时没有异常。问题似乎与通过这个“invoke”方法传递字符串数组有关。 - stav
正如你所说,你不相信它! - Alvaro Maleno Alferez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接