Java 的可变参数(Varargs) 方法让我们调用起来很方便,不需要总是去构造一个数组来传递不定数量的参数,而且还可以作为方法的一个可选参数,如
void foo(int id, String...name) {
String yourName = name.length == 0 ? "Anonymous" : name[0];
......
}
想要告诉名字就调用 foo(1, "Yanbin")
, 不想的话就用 foo(1)
.
但我们在使用 Java Varargs 时,当变参类型定义为 Object...objects
时就要当心了,因为 Object 类型的包容性原因一不小心就可以掉到坑里去了,例如下面的方法
void foo(Object...objects) {
Arrays.stream(objects).forEach(System.out::println());
}
当引用类型是 Object[]
时调用没问题,下面代码调用可以得到预期的结果
Object[] input = new Object[]{"1", "2"};
foo(input);
输出为
1
2
可是把引用类型变成 Object[]
后可能就要引发 Bug 了,看下面代码的输出
Object input = new Object[]{"1", "2"};
foo(input);
输出类似如下
[Ljava.lang.Object;@5d099f62
foo(Object...objects)
只识别到了一个参数,它把 input 整体作为可变参 objects
的第一个元素,因为数组 Object[]
也是一个 Object
实例.
这里像是一个多态的情形,行为应该是运行时决定的,与引用类型应无关, 假乎应该类似如下的情型
void foo(Animal animal) {...}
Animal animal = new Dog();
foo(animal); //1Dog dog = new Dog();
foo(dog); //2
//1 和 //2 两种调用方式的行为应该是一样的。
但它们又是不一样的,前面代码
Object input = new Object[] {"1", "2"};
foo(input); //3
更像是在让 foo(input)
调用从两个重载方法
foo(Object object);
//和
foo(Object...objects);
中选择一个更匹配的方法来调用,所以第一个胜出,因为重载方法的匹配是依据引用类型的。
//3 所示的情况可能较少出现,可有时候我们依赖于一个方法来获得 Object[]
类型返回值时就得当心啦,例如我们放大一个方法的返回值类型为 Object 来适应可能的多种类型返回值
Object bar(int type) {
if(type == 1) {
return new Object[]{"33", "44"};
} else {
return "Hello";
}
然后我们心里很清在传入 1 时可以得到一个 Object[]
类型返回值,所以想当然的调用 foo() 方法,像
foo(bar(1))
于是就掉到陷进里了,把 new Object[]{"33", "44"}
以一个整体作为了 Object...objects
的第一个元素了,输出了 [Ljava.lang.Object;@31befd9f
这样的内容,而不是遍历这个数组。
为避免上面的潜在问题,当参数的引用类型是 Object 的对象数组需要显式的转型
foo((Object[]) input);
foo((Object[] bar(1));
就像是我们用整型参数想要匹配长整型重载方法时的做法
foo(int number); //a
foo(long number); //bfoo((long)100); //这样虽然 100 是一个整型数,显式转型就能调用到 //b 方法。
本文所述及的陷进也就是在变参类型为 Object...objects
才会出现的,要是别的类型倒不用担心,如 String...strings
是不可能把传入的数组整体作为第一个参数的,因为 String 容不下数组的。
本文的情型有些类似于 Scala 使用变参的方法, 请参考 Java 和 Scala 调用变参的方式,只是 Scala 的要求更为苛刻。
本文链接 https://yanbin.blog/java-varargs-method-error-prone/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。