在java中可以用可变长度参数,这样可以给调用者更大的灵活度,可变长度参数在编译后就是一个数组参数,不用担心可变长度参数是null的问题,不传的话就是一个长度为0的数组,可变长度参数只能是最后一个参数。
但是在用反射调用有可变长度参数的方法时要注意,如下图:
public String testa(Object... args){
for (Object arg : args) {
System.out.println(arg);
}
return "a";
}
@Test
public void test28() throws InvocationTargetException, IllegalAccessException {
Method method = ReflectionUtil.getMethodByName(NormalTest.class, "testa");
//报错 argument type mismatch
// method.invoke(this, 123);
//报错 argument type mismatch
// method.invoke(this, new Object[]{123});
//报错 wrong number of arguments
// method.invoke(this, new Object[]{123, 456});
//报错 argument type mismatch
// method.invoke(this, new Object[]{new int[]{123, 456}});
//正确
method.invoke(this, new Object[]{new Integer[]{123, 456}});
}
从上面看出参数要被包成数组2次,至于为什么2次不明白。
但是下面的例子又说明参数包装成1次也是可以的,如下:
public String testa(int a, int b) {
System.out.println(a + b);
return "testa";
}
public String testb(int... a){
for (int i : a) {
System.out.println(i);
}
return "testb";
}
public String testc(int a, int b, int... c){
System.out.println(a + b);
for (int i : c) {
System.out.println(i);
}
return "testc";
}
@Test
public void test28() throws InvocationTargetException, IllegalAccessException {
Method testa = ReflectionUtil.getMethodByName(NormalTest.class, "testa");
//参数个数固定,直接给参数
System.out.println(testa.invoke(this, 1, 2));
Method testb = ReflectionUtil.getMethodByName(NormalTest.class, "testb");
//方法参数是可变参数,需要包2层数组
System.out.println(testb.invoke(this, new Object[]{new int[]{1,2}}));
//正确,和上面结果一样,但是有的情况下需要包2层数组,这个包一层也可以,不知道原因
System.out.println(testb.invoke(this, new int[]{1,2}));
//直接给参数,报wrong number of arguments
// System.out.println(testb.invoke(this, 1, 2));
//报错 argument type mismatch
// System.out.println(testb.invoke(this, 1));
Method testc = ReflectionUtil.getMethodByName(NormalTest.class, "testc");
//正确
System.out.println(testc.invoke(this, 1, 2, new int[]{1, 2}));
//报错 argument type mismatch
System.out.println(testc.invoke(this, 1, 2, new Object[]{new int[]{1,2}}));
}
为什么通过反射就要包装成数组,甚至包装2次,不明白。
参考这个作者的回答:https://stackoverflow.com/questions/53694634/call-variable-argument-method-by-reflection
Method.invoke(object, Object...)
本身就需要将所有参数封装成一个Object数组,而方法可变长度参数也只是数组参数的语法糖而已,因此要想通过反射调用testa(Object... args)
即test(Object[] args)
就必须用new Object[]{new Object[]{}}
,最外层Object数组是给Method.invoke用的,最里层数组是你的方法需要的参数。
再说另一个问题,数组是可以协变的,即Object[] objects = new Integer[1];是合法的,但是Object[] objects = new int[1];或Integer[] ints = new int[1];是非法的。
在调用可变长度参数时,如果你给的参数类型和方法参数类型相同或可协变的或者可转换的,则直接把你的参数转换后传递给方法参数,如果不能转换,会在外层包装一层Object数组,把你的参数作为Object数组中的元素传给方法参数。看下图:
public String testa(Object... args){
for (Object arg : args) {
System.out.println(arg);
}
return "a";
}
public String testb(int... args){
for (Object arg : args) {
System.out.println(arg);
}
return "b";
}
@Test
public void test28() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
this.testa(123);
//int[]不能转换成Object[],因此会被包在Object[]里面
this.testa(new int[]{123});
//这种写法和上面是一样的
this.testa(new Object[]{new int[]{123}});
this.testa(new Integer[]{123});
this.testa(new Object[]{123});
System.out.println("======");
this.testb(123);
//int[]能直接转换为方法需要的参数
this.testb(new int[]{123});
//编译报错
// this.testb(new Integer[]{123});
}
打印的结果是
123
[I@783e6358
[I@17550481
123
123
======
123
123
疑问:
public void testa(Object... args){
}
public void testa(Object[] args){
}
在编译后都会变成testa(Object[] args)这种形式,为什么在调用的时候testa()调用可变长度参数方法编译通过,调用数组参数编译报错,是在javac的过程中检查方法参数类型做转换吗?
网友评论