第四十二条 慎用可变参数

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){}

就这样,除非必要,尽量不要使用可变参数方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值