第42条 慎用可变参数
20150628 星期日 北京
java1.5版本之后,增加可变参数的方法,一般称为可匹配不同长度的变量的方法.可变参数方法接受0个或者多个指定类型的参数.
可变参数机制通过先创建一个数组,数组的大小在调用位置所传递的参数数量,然后将参数值传到数组中,最后将数组传给方法.
------>01.0个或者多个指定类型的参数
例子[复习使用],可变参数方法,带有int参数的序列,返回他们的总和.
public class Arity {
publicstatic void main(String[] args) {
System.err.println(sum(newint[]{1, 2, 3}));
}
staticint sum (int ... args) {
intsum = 0;
for(int arg : args) {
sum+= arg;
}
returnsum;
}
}
和预期效果一致,结果是6.
------>02.1个或者多个指定类型的参数
有必要编写需要1个或者多个某种类型参数的方法,而不是需要0个或者多个.
假如要计算多个int参数的最小值,如果客户端没有传递参数,那方法就不太友好,可以在运行时检查数组长度.
staticint min (int ... args) {
if(args == null) {
throw new IllegalArgumentException("Toofew arguments");
}
intmin = args[0];
for(int arg : args) {
if(min > arg) {
min= arg;
}
}
returnmin;
}
思考:上面程序存在什么问题呢?
最严重的问题,如果客户端调用方法没有传递数据,那么就会在运行时而不是编译时失败;
另外问题,代码不美观,必须进行显示检查,同时变量min必须初始化才能使用for-each循环.
如何解决呢?声明该方法带两个参数,一个是指定类型的正常参数,另一个是指定类型的varargs参数(可变),这样就解决前面两个问题.
staticint min (int firstArg, int ... args) {
intmin = firstArg;
for(int arg : args) {
if(min > arg) {
min= arg;
}
}
returnmin;
}
[经验:能编译时检查处理的问题,不要放在运行时处理;能不使用检查,那么尽可能不使用,如果前提允许.]
需要方法带入不定数量的参数时,可变参数非常有效.
可变参数是为printf而设计,在java1.5后加入平台.printf和反射机制都从可变参数中获益匪浅.
可以将数组作为final参数的现有方法改造成可变参数代替,而不影响客户端.可以这么做,但是不意味应该这么做!
考虑Arrays.asList()的情形,此方法从来都不是设计成用来将多个参数集中到一个列表中的.
[java1.6API中Arrays.asList(T... a) 返回一个受指定数组支持的固定大小的列表.就是这个用途!]
不考虑java1.4及其以前版本.
int[] digits = {1, 2, 3, 4, 5};
//编译不通过
List<Integer> ints = Arrays.asList(digits);
//编译通过
List<Integer> ints = Arrays.asList(1, 2, 3,4, 5);
//1.5之前编译不通过,现在编译通过,结果没用
System.err.println(Arrays.asList(new int[]{1, 2,3}));
//result: [[I@1db9742]
在1.5发行版本后,Arrays类补充完整的Arrays.toString()方法(不是可变参数方法),专门为任何类型的数组转变为字符串而设计.原来使用Arrays.asList(),现在使用Arrays.toString();
结论:不必改造具有final数组参数的每个方法;只当确定是在数量上不定的值上执行调用时才使用可变参数.[这段没搞懂,实在搞不明白作者想要表达什么含义]
------>03.可变参数和性能
重视性能的情况,使用可变参数机制要小心.因为可变参数方法的每次调用都会导致进行一次数组分配和初始化.
如果经验确定无法承受这一成本,却又需要可变参数的灵活性,折中方案如下:
假设确定某个方法95%的调用会有3个或者更少的参数,那么就声明该方法的5个重载,每个重载方法带有0到3个普通参数,单参数个数超过3个,就使用可变参数方法.
publicvoid foo () {}
publicvoid foo (int a) {}
publicvoid foo (int a, int b) {}
publicvoid foo (int a, int b, int c) {}
publicvoid foo (int a, int b, int c, int ... rest) {}
所有调用中只有5%的参数数量超过3个的调用需要创建数组,虽然处理方式貌似不恰当,但是会非常实用.
------>04. EnumSet案例
EnumSet静态工厂实用上面的处理方式,最大限度地减少创建枚举集合的成本,这么做的必要性在于枚举集合在位域提供性能方面有竞争力的替代方案.
下面是EnumSet API中的源码
publicstatic <E extends Enum<E>> EnumSet<E> of(E e) {
EnumSet<E> result = noneOf(e.getDeclaringClass());
result.add(e);
return result;
}
publicstatic <E extends Enum<E>> EnumSet<E> of(E e1, E e2) {
EnumSet<E> result = noneOf(e1.getDeclaringClass());
result.add(e1);
result.add(e2);
return result;
}
publicstatic <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3) {
EnumSet<E> result = noneOf(e1.getDeclaringClass());
result.add(e1);
result.add(e2);
result.add(e3);
return result;
}
publicstatic <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, Ee4) {
EnumSet<E>result = noneOf(e1.getDeclaringClass());
result.add(e1);
result.add(e2);
result.add(e3);
result.add(e4);
return result;
}
publicstatic <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, Ee4,E e5){
EnumSet<E> result = noneOf(e1.getDeclaringClass());
result.add(e1);
result.add(e2);
result.add(e3);
result.add(e4);
result.add(e5);
return result;
}
publicstatic <E extends Enum<E>> EnumSet<E> of(E first, E... rest){
EnumSet<E> result = noneOf(first.getDeclaringClass());
result.add(first);
for(E e : rest)
result.add(e);
return result;
}
在定义参数数目不定的方法时,可变参数方法是有效方式,但是不能过度乱用.