Java反射调用报错java.lang.IllegalArgumentException wrong number of arguments

Posted by Night Field's Blog on November 20, 2019

问题描述

Target.java有一个execute()方法,用一个String数组作为参数

1
2
3
4
5
public class Target {
    public void execute(String[] args) {
        System.out.println("call execute method with parameter type String[]");
    }
}

用如下方式,通过反射去调用这个方法

1
2
3
4
5
6
7
8
9
public class Test {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        String[] parameters = {"1"}; // parameter array
        Class targetClass = Class.forName("Target");// get target class "Target"
        Object instance= targetClass.newInstance();
        Method execute = targetClass.getDeclaredMethod("execute", String[].class);// get target method "execute"
        execute.invoke(instance, parameters);// invoke method
    }
}

结果控制台出现报错

1
2
3
4
5
6
Exception in thread "main" java.lang.IllegalArgumentException: wrong number of arguments
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at Test.main(Test.java:16)

问题分析

找到Method.invoke方法,其实它接收的是一个变长参数(Varargs)

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

当编译器发现类似

1
method.invoke(object, arg1, arg2)

这样的表示时,会隐式地创建一个数组,类似new Object [] {arg1, arg2},然后将该数组作为invoke方法的参数。 但是如果目标方法的参数本来就是一个数组的时候,如

1
method.invoke(object, Object[])

编译器会认为你已经将所有的参数放到数组里面了,从而不会再次包装。于是在这种情况下,作为参数传递给目标方法的,其实是数组里面的元素,而不是数组本身。

所以我们来回看上面的例子,其实main方法最终通过反射尝试调用的是,以一个String类型作为参数的execute方法,我们来做个试验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Target {
    public void execute(String[] args) {
        System.out.println("call execute method with parameter type String[]");
    }

    public void execute(String arg) {
        System.out.println("call execute method with parameter type String");
    }
}

public class Test {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        String[] parameters = {"1"}; // parameter array
        Class targetClass = Class.forName("Target");// get target class "Target"
        Object instance= targetClass.newInstance();
        Method execute = targetClass.getDeclaredMethod("execute", String.class);// get target method "execute"
        execute.invoke(instance, parameters);// invoke method
    }
}

最终打印出来的是(注意两个方法参数是不一样的)

call execute method with parameter type String

问题解决

其实这种情况的解决方法很简单,只要把数组包装成Object数组的第一个元素就好了,这样编译器就会把数组本身作为参数,传递给目标方法了,如下:

1
2
3
4
5
6
7
8
9
public class Test {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        String[] parameters = {"1"}; // parameter array
        Class targetClass = Class.forName("Target");// get target class "Target"
        Object instance = targetClass.newInstance();
        Method execute = targetClass.getDeclaredMethod("execute", String[].class);// get target method "execute"
        execute.invoke(instance, new Object[] {parameters});// invoke method
    }
}

总结

当用反射调用方法时,如果目标方法的入参是一个数组,则要把数组包装到另一个Object数组中。