调用将字符串数组作为参数的方法导致 IllegalArgumentException

sta*_*tav 3 java reflection

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

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();
}
Run Code Online (Sandbox Code Playgroud)

(相同的方法,除了一个采用 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
Run Code Online (Sandbox Code Playgroud)

(这里的“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)
Run Code Online (Sandbox Code Playgroud)

为什么是这样?

Swe*_*per 7

观察invoke有这个签名:

public Object invoke(Object obj, Object... args)
Run Code Online (Sandbox Code Playgroud)

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

someStaticMethod.invoke(null, param1, param2, param3);
Run Code Online (Sandbox Code Playgroud)

会打电话:

someStaticMethod(param1, param2, param3);
Run Code Online (Sandbox Code Playgroud)

另一方面, 的类型args实际上只是Object[],并且存在从String[]到的转换Object[],因为数组是协变的。

因此,当您将String[]of传递strArr给 时invoke,可能会发生以下两种情况之一:

  • strArr转换为Object[],并且每个都String被视为要调用的方法的参数。
  • 整体strArr被视为要调用的方法的参数之一,就像非数组类型一样。

编译器只是恰巧喜欢第一个(见这里。注意可变参数数量调用如何优先级最低),所以你实际上传递参数"1""2""3"的方法,而不是一个单一的String[]

另一方面,int[]不能直接转换为Object[],因为int是primitive,所以编译器只能选择上面的第二个选项。

强制第二个选项的一种方法是强制转换为Object

clazz.getMethod("bar", String[].class).invoke(null, (Object)strArr);
Run Code Online (Sandbox Code Playgroud)

另一种方法是创建另一个Object[]包装String[]

clazz.getMethod("bar", String[].class).invoke(null, new Object [] { strArr });
Run Code Online (Sandbox Code Playgroud)

  • 如果有人想知道为什么编译器更喜欢第一种行为,那是因为在引入 *varargs* 之前 `invoke(null, strArr)` 是一个有效的调用,因此它的行为不允许改变。相反,任何不可分配给 `Object[]` 的参数在 *varargs* 之前都是无效的,因此现在将 `int[]` 参数填充到幕后的数组中,将其视为不存在兼容性问题反射方法的单个参数。 (2认同)