在S.O.上回答this other question ,我遇到了一个我无法理解的奇怪问题。为什么反射'invoke'方法类需要将varargs参数包装在一个数组中才能工作?
使用vararg参数的其他类方法运行正常,只是以正常的方式调用它们......
public class Main {
public static void main(String[] args){
try {
Class<?> p = Main.class;
String[] arguments1 = {"ciao"};
String[] arguments2 = {"salve"};
String[] arguments3 = {"buonasera"};
Method m = p.getDeclaredMethod("showIt",String[].class);
//this is ok
showIt(arguments1);
//this is ok
m.invoke(null, new Object[]{arguments2});
//this throws IllegalArgumentException!!
m.invoke(null, arguments3);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void showIt(String[] result) {
System.out.println(result[0]);
}
}
调用方法有什么特别之处?
答案 0 :(得分:6)
这是因为varargs和数组参数类型存在歧义。基本上,当方法声明为
时void takesSomeArgs(Object... values) {
}
这大致相当于
void takesSomeArgs(Object[] values) {
}
问题是,现在对以下调用有两种可能的(同样有效的)解释:
Object[] example = { "foo", "bar" };
takesSomeArgs(example);
第一种解释是
values
类似于Object[1] { Object[1] { "foo", "bar" } }
(varargs解释)values
类似于Object[2] { "foo", "bar" }
,因为Object...
本质上是Object[]
,因此,我们可以传递数组引用从example
直接作为value
。根据JLS 15.12.2.3的规则,编译器订阅第二种解释(在invoke(Object, Object...)
本身的调用上,这是可变的),这导致尝试传递 2 < / em>你的方法的参数 - 失败,因为该方法只需要一个(编辑注意,我在这里描述了错误的症状;请参阅Json的答案,这是正确的)。换句话说,就像这样的呼叫:
m.invoke(receiver, new Object[] { "a", "b", "c" });
和
m.invoke(receiver, "a", "b", "c");
实际上是完全一样的。因此需要写
m.invoke(receiver, new Object[] { new Object[] { "a", "b", "c" } }); // (X)
(明确地)或
m.invoke(receiver, (Object) (new Object[] { "a", "b", "c" }));
注意强制转换:它使参数不再可转换为Object[]
并强制编译器生成等效于(X)的代码。另请注意,new Object[] { ... }
不是字面意思,而只是为了使所涉及的值的类型清晰。
在我看来,这是一个令人遗憾的副作用,Java语言的设计者希望能够(并且方便)实现像
这样的事情。void logError(String control, Object... parameters) {
// Forwarding the unknown number of arguments from one variadic method
// to the next: String.format(String, Object...)
log.message(String.format(control, parameters));
}
没有引入新语法。
答案 1 :(得分:6)
让我们说我们有方法
public static void someMethod(String... arguments){
// implementation is irrelevant but will add it for demo purpose
System.out.println("I have "+arguments.length +"arguments which are:");
for (String arg:arguments)
System.out.println(arg);
}
您可能知道vararg实际上是数组,但它很特殊,因为它允许以表格形式传递参数
someMethod("arg1")
someMethod("arg1", "arg2")
。 但不要忘记,因为...
是数组接受数组参数,如
someMethod(new MyArgumentsType[]{arg1, arg2, arg3})
。我们知道invoke
被声明为public Object invoke(Object obj, Object... args)
现在要调用需要showIt(String[] result)
作为参数的方法String[]
。
您以invoke
方式调用m.invoke(null, arguments3);
方法。由于每个数组都可以被视为Object[]
invoke
,因此将理解您在数组中传递一组参数(请参阅我的示例中调用someMethod
的第三种方式)。这种方法只能找到一个参数:"buonasera"
。但showIt
需要String[]
而不是String
,因此您会看到“参数类型不匹配”异常。
要摆脱这个问题,你有两个选择。
将String数组包装在其他数组中,就像在m.invoke(null, new Object[] { arguments2 });
中一样。这样varargs会将该Object数组的争用视为参数列表,因此它会将包装的String []数组视为正确的参数。
将数组转换为Object,以显示该数组只是一个参数,而不是参数集
在我的someMethod
示例
m.invoke(null, (Object) arguments3);
答案 2 :(得分:3)
是
的两种解释m.invoke(null, arguments3);
第一种解释相当于
m.invoke(null, (Object)arguments3);
第二种解释相当于
m.invoke(null, (Object[])arguments3);
请注意,这与
不同m.invoke(null, new Object[] { arguments3 });
这非常重要。在第二种解释中,String[]
本身正在转换为Object[]
,并在args
参数中用于invoke
; arguments3
被视为args
,不被视为组成args
的数组的元素。
Java默认会选择第二种解释。在这种情况下,它将打开Object[]
的元素并将其用作showIt
的参数。因此,从showIt
的角度来看,它看起来像是被调用为
showIt("buonasera")
这显然无效,因为showIt
期待的是String[]
,而不是String
。
事实上,如果你阅读Method.invoke的文档,你会看到:
IllegalArgumentException
- 如果方法是实例方法,并且指定的对象参数不是声明底层方法(或其子类或实现者)的类或接口的实例;如果实际参数和形式参数的数量不同;如果原始参数的展开转换失败; 或者,如果在可能的解包后,参数值无法通过方法调用转换转换为相应的形式参数类型。
这正是这里发生的事情。同样,因为看起来使用单个showIt
参数调用String
,而不是单个String[]
参数。
但在第一种解释中,我们将String[]
视为Object
的一个实例,并将其用作varargs Object[]
参数中args
中的单个元素。所以,如果你明确地告诉Java它不能进行String[]
到Object[]
的隐式转换,那么String[]
将最终作为第一个形式参数传递,而不是使用String[]
的元素作为showIt
的形式参数。
因此,
m.invoke(null, (Object)arguments3);
工作得很好。
答案 3 :(得分:-1)
如果您有两个重载方法:
showIt(String firstname, String lastname);
showIt(String names ...);
当你致电invoke
时,java必须知道你正在调用哪种方法。传递两个参数调用第一个,传递一个字符串数组调用第二个。