java 1.5 版本中,新增了可变参数方法,可变参数的个数是从0开始,理论上可以无限制大的个数,可变参数机制通过创建数组,把可变参数装到数组里,数组的大小为可变参数的个数,然后再将数组传递给方法,可变参数就是这么工作的。看个例子,求可变参数中的综合,
static int sum(int... args) {
int sum=0;
for(int arg : args)
sum += arg;
return sum;
}
sum(1,2,3)的值为6;sum()的值为0。这样用起来很方便,但如果有时候,需要编写1个或多个参数的方法,不需要0个,怎么办?我们在方法中对参数进行校验?
static int min(int... args) {
if (args.length == 0)
throw new IllegalArgumentException("Too few arguments");
int min = args[0];
for (int i = 1; i < args.length; i++)
if (args[i] < min)
min = args[i];
return min;
}
这个方法中,我们对参数做了校验,如果可变参数长度为0,则提示异常,只有大于0时,才能执行逻辑操作。这么做有个小问题,如果用户传递的可变参数长度为0,则会运行失败,并且代码不美观,我们可以提出相对应的方案,一种是修改 throw new IllegalArgumentException("Too few arguments"); 方法,改为显示校验,例如返回 0,这种方法不太好;另外一种是修改方法,例如
static int min(int firstArg, int... remainingArgs) {
int min = firstArg;
for (int arg : remainingArgs)
if (arg < min)
min = arg;
return min;
}
改成这样,就能保证至少有一个参数,并且比较简明清晰。可变参数这样用起来比较好,但不意味着可变参数可以随意用,例如 Arrays.asList() 的代码,意思是把多个参数转换为一个list类型的数据,
List<String> homophones = Arrays.asList("to", "too", "two");
System.out.println(homophones);
List<Integer> asList = Arrays.asList(1,2,3);
System.out.println(asList);
int[] intValus = { 1, 2, 3 };
System.out.println(Arrays.asList(intValus));
执行代码,打印的结果是 [to, too, two] [1, 2, 3] [[I@6d06d69c] ,这是为什么呢?看 Arrays 的源码,
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable {
private static final long serialVersionUID = -2764017481108945198L;
private final E[] a;
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
@Override
public int size() {
return a.length;
}
...
}
我们知道,调用asList(T... a)方法后,返回的结果是一个 Arrays 内部的 ArrayList 集合,这个集合里面没有重写toString()方法,那么调用的是基类AbstractCollection里的toString(),
public String toString() {
Iterator<E> it = iterator();
if (! it.hasNext())
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = it.next();
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}
由此可见,打印的还是List里面对象的toString(),并且把它们装到了中括号里。再返回上面的代码,List<String> homophones = Arrays.asList("to", "too", "two");所以打印时是字符串的toString,即 [to, too, two]。List<Integer> asList = Arrays.asList(1,2,3); 这个List里的对象是Integer,所以打印的是int的值,即 [1, 2, 3]。而 int[] intValus = { 1, 2, 3 };List<int[]> valus = Arrays.asList(intValus); 这个返回的List里面的对象是 int[],并且List集合长度为1,至于一个数组,因此打印的是数组toString,即数组的地址值,[[I@6d06d69c]。看到这里大家就明白了,如果传入的是对象,那么打印的就是对象的toString,我们如果重写了对象的toString()方法,就能看到了对应的值,否则,如果没有重写自己的toString()方法,那么打印的就是父类的toString()方法,顶层基类就是Object了。同样道理,如果我们把字符串数组传递进去,会是什么呢
String[] strs = {"to", "too", "two"};
List<String> asList3 = Arrays.asList(strs);
System.out.println(asList3);
我们发现,Arrays.asList(strs) 的类型是 List<String>而不是 List<String[]>,所以打印的结果是 [to, too, two]。 为什么会是这样呢,记得前面泛型提过,泛型不要和数组混着用,这里大概是泛型推导,String 比较特殊,具体的原因我也没闹明白,暂且算是特殊情况吧。如果高手看到了,希望留言,指导一下。
对于上面的int[] intValus = { 1, 2, 3 }; System.out.println(Arrays.asList(intValus)); 打印的是list里对象数组的地址值 [[I@6d06d69c],怎么打印数组里的每一个值呢,我们可以用另外一个方法,Arrays.toString(a)方法,打印的是 [1, 2, 3] ,看一下源码
public static String toString(int[] a) {
if (a == null)
return "null";
int iMax = a.length - 1;
if (iMax == -1)
return "[]";
StringBuilder b = new StringBuilder();
b.append('[');
for (int i = 0; ; i++) {
b.append(a[i]);
if (i == iMax)
return b.append(']').toString();
b.append(", ");
}
}
原来,这个方法就是遍历数组的,通过 StringBuilder 把数组的值遍历,并加上了中括号及逗号分隔符,然后以 String 字符串的形式返回,所以才有了 [1, 2, 3] 。
在重视性能的情况下,使用可变参数要小心,可变参数方法每次调用都会导致进行一次数组分配和初始化,所以可以写几个用到概率比较高的方法,多余的参数再用可变参数来表示
public void foo(){}
public void foo(int a1){}
public void foo(int a1, int a2){}
public void foo(int a1, int a2, int a3){}
public void foo(int a1, int a2, int a3, int... rest){}
就这样,除非必要,尽量不要使用可变参数方法。