java使用省略号代替多参数(参数类型... 参数名)

J2SE 1.5提供了“Varargs”机制。借助这一机制,能够定义能和多个实参相匹配的形参。从而,能够用一种更简单的方式,来传递个数可变的实参。本文介绍这一机制的使用方法,以及这一机制与数组、泛型、重载之间的相互做用时的若干问题。

到J2SE 1.4为止,一直没法在Java程序里定义实参个数可变的方法——由于Java要求实参(Arguments)和形参(Parameters)的数量和类型都必须逐一匹配,而形参的数目是在定义方法时就已经固定下来了。尽管能够经过重载机制,为同一个方法提供带有不一样数量的形参的版本,可是这仍然不能达到让实参数量任意变化的目的。java

然而,有些方法的语义要求它们必须能接受个数可变的实参——例如著名的main方法,就须要能接受全部的命令行参数为实参,而命令行参数的数目,事先根本没法肯定下来。数组

对于这个问题,传统上通常是采用“利用一个数组来包裹要传递的实参”的作法来应付。app

1. 用数组包裹实参

“用数组包裹实参”的作法能够分红三步:首先,为这个方法定义一个数组型的参数;而后在调用时,生成一个包含了全部要传递的实参的数组;最后,把这个数组做为一个实参传递过去。ide

这种作法能够有效的达到“让方法能够接受个数可变的参数”的目的,只是调用时的形式不够简单。函数

J2SE 1.5中提供了Varargs机制,容许直接定义能和多个实参相匹配的形参。从而,能够用一种更简单的方式,来传递个数可变的实参。编码

2. 定义实参个数可变的方法

只要在一个形参的“类型”与“参数名”之间加上三个连续的“.”(即“...”,英文里的句中省略号),就可让它和不肯定个实参相匹配。而一个带有这样的形参的方法,就是一个实参个数可变的方法。设计

清单1:一个实参个数可变的方法

private static int sumUp( int... values) { 

注意,只有最后一个形参才能被定义成“能和不肯定个实参相匹配”的。所以,一个方法里只能有一个这样的形参。另外,若是这个方法还有其它的形参,要把它们放到前面的位置上。code

编译器会在背地里把这最后一个形参转化为一个数组形参,并在编译出的class文件里做上一个记号,代表这是个实参个数可变的方法。orm

清单2:实参个数可变的方法的秘密形态

private static int sumUp( int[] values) { 

因为存在着这样的转化,因此不能再为这个类定义一个和转化后的方法签名一致的方法。

清单3:会致使编译错误的组合

private static int sumUp( int... values) { 

private static int sumUp( int[] values) { 

3. 调用实参个数可变的方法

只要把要传递的实参逐一写到相应的位置上,就能够调用一个实参个数可变的方法。不须要其它的步骤。

清单4:能够传递若干个实参

sumUp( 1, 3, 5, 7);

在背地里,编译器会把这种调用过程转化为用“数组包裹实参”的形式:

清单5:偷偷出现的数组建立

sumUp( new int[]{1, 2, 3, 4}); 

另外,这里说的“不肯定个”也包括零个,因此这样的调用也是合乎情理的:

清单6:也能够传递零个实参

sumUp (); 

这种调用方法被编译器秘密转化以后的效果,则等同于这样:

清单7:零实参对应空数组

sumUp(new int[] {}); 

注意这时传递过去的是一个空数组,而不是null。这样就能够采起统一的形式来处理,而没必要检测到底属于哪一种状况。

4. 处理个数可变的实参

处理个数可变的实参的办法,和处理数组实参的办法基本相同。全部的实参,都被保存到一个和形参同名的数组里。根据实际的须要,把这个数组里的元素读出以后,要蒸要煮,就能够随意了。

清单8:处理收到的实参们

private static int sumUp(int...  values) { 
    int sum = 0; 
    for (int i = 0; i <  values.length; i++) { 
        sum +=  values[i]; 
    } 
    return sum; 

5. 转发个数可变的实参

有时候,在接受了一组个数可变的实参以后,还要把它们传递给另外一个实参个数可变的方法。由于编码时没法知道接受来的这一组实参的数目,因此“把它们逐一写到该出现的位置上去”的作法并不可行。不过,这并不意味着这是个不可完成的任务,由于还有另一种办法,能够用来调用实参个数可变的方法。

在J2SE 1.5的编译器的眼中,实参个数可变的方法是最后带了一个数组形参的方法的特例。所以,事先把整组要传递的实参放到一个数组里,而后把这个数组做为最后一个实参,传递给一个实参个数可变的方法,不会形成任何错误。借助这一特性,就能够顺利的完成转发了。

清单9:转发收到的实参们

public class PrintfSample { 
    public static void main(String[] args) { 
        //打印出“Pi:3.141593 E:2.718282” 
        printOut("Pi:%f E:%f/n", Math.PI, Math.E); 
    } 
    private static void printOut(String format,  Object... args) { 
        //J2SE 1.5里PrintStream新增的printf(String format, Object... args)方法 
        System.out.printf(format,  args); 
    } 

6. 是数组?不是数组?

尽管在背地里,编译器会把能匹配不肯定个实参的形参,转化为数组形参;并且也能够用数组包了实参,再传递给实参个数可变的方法;可是,这并不表示“能匹配不肯定个实参的形参”和“数组形参”彻底没有差别。

一个明显的差别是,若是按照调用实参个数可变的方法的形式,来调用一个最后一个形参是数组形参的方法,只会致使一个“cannot be applied to”的编译错误。

清单10:一个“cannot be applied to”的编译错误

private static void testOverloading( int[] i) { 
    System.out.println("A"); 

public static void main(String[] args) { 
    testOverloading( 1, 2, 3);//编译出错 

因为这一缘由,不能在调用只支持用数组包裹实参的方法的时候(例如在不是专门为J2SE 1.5设计第三方类库中遗留的那些),直接采用这种简明的调用方式。

若是不能修改原来的类,为要调用的方法增长参数个数可变的版本,而又想采用这种简明的调用方式,那么能够借助“引入外加函数(Introduce Foreign Method)”和“引入本地扩展(Intoduce Local Extension)”的重构手法来近似的达到目的。

7. 当个数可变的实参遇到泛型

J2SE 1.5中新增了“泛型”的机制,能够在必定条件下把一个类型参数化。例如,能够在编写一个类的时候,把一个方法的形参的类型用一个标识符(如T)来表明,至于这个标识符到底表示什么类型,则在生成这个类的实例的时候再行指定。这一机制能够用来提供更充分的代码重用和更严格的编译时类型检查。

不过泛型机制却不能和个数可变的形参配合使用。若是把一个能和不肯定个实参相匹配的形参的类型,用一个标识符来表明,那么编译器会给出一个“generic array creation”的错误。

清单11:当Varargs赶上泛型

private static  <T> void testVarargs( T... args) {//编译出错 

形成这个现象的缘由在于J2SE 1.5中的泛型机制的一个内在约束——不能拿用标识符来表明的类型来建立这一类型的实例。在出现支持没有了这个约束的Java版本以前,对于这个问题,基本没有太好的解决办法。

不过,传统的“用数组包裹”的作法,并不受这个约束的限制。

清单12:能够编译的变通作法

private static  <T> void testVarargs( T[] args) { 
    for (int i = 0; i < args.length; i++) { 
        System.out.println(args[i]); 
    } 

8. 重载中的选择问题

Java支持“重载”的机制,容许在同一个类拥有许多只有形参列表不一样的方法。而后,由编译器根据调用时的实参来选择到底要执行哪个方法。

传统上的选择,基本是依照“特殊者优先”的原则来进行。一个方法的特殊程度,取决于为了让它顺利运行而须要知足的条件的数目,须要条件越多的越特殊。

在引入Varargs机制以后,这一原则仍然适用,只是要考虑的问题丰富了一些——传统上,一个重载方法的各个版本之中,只有形参数量与实参数量正好一致的那些有被进一步考虑的资格。可是Varargs机制引入以后,彻底能够出现两个版本都能匹配,在其它方面也别无二致,只是一个实参个数固定,而一个实参个数可变的状况。

遇到这种状况时,所用的断定规则是“实参个数固定的版本优先于实参个数可变的版本”。

清单13:实参个数固定的版本优先

public class OverloadingSampleA { 
    public static void main(String[] args) { 
        testOverloading( 1);//打印出A 
        testOverloading( 1, 2);//打印出B 
        testOverloading( 1, 2, 3);//打印出C 
    } 
    private static void testOverloading( int i) { 
        System.out.println("A"); 
    } 
    private static void testOverloading( int i, int j) { 
        System.out.println("B"); 
    } 
    private static void testOverloading( int i, int... more) { 
        System.out.println("C"); 
    } 

若是在编译器看来,同时有多个方法具备相同的优先权,它就会陷入没法就到底调用哪一个方法做出一个选择的状态。在这样的时候,它就会产生一个“reference to 被调用的方法名 is ambiguous”的编译错误,并耐心的等候做了一些修改,足以避免除它的迷惑的新源代码的到来。

在引入了Varargs机制以后,这种可能致使迷惑的状况,又增长了一些。例如如今可能会有两个版本都能匹配,在其它方面也一模一样,并且都是实参个数可变的冲突发生。

清单14:左右都不是,为难了编译器

public class OverloadingSampleB { 
    public static void main(String[] args) { 
        testOverloading(1, 2, 3);//编译出错 
    } 
    private static void testOverloading( Object... args) { 
    } 
    private static void testOverloading( Object o, Object... args) { 
    } 

另外,由于J2SE 1.5中有“Autoboxing/Auto-Unboxing”机制的存在,因此还可能发生两个版本都能匹配,并且都是实参个数可变,其它方面也如出一辙,只是一个能接受的实参是基本类型,而另外一个能接受的实参是包裹类的冲突发生。

清单15:Autoboxing/Auto-Unboxing带来的新问题

public class OverloadingSampleC { 
    public static void main(String[] args) { 
        /* 编译出错 */ 
        testOverloading( 1, 2); 
        /* 仍是编译出错 */ 
        testOverloading( new Integer(1), new Integer(2)); 
    } 
    private static void testOverloading( int... args) { 
    } 
    private static void testOverloading( Integer... args) { 
    } 

9. 概括总结

和“用数组包裹”的作法相比,真正的实参个数可变的方法,在调用时传递参数的操做更为简单,含义也更为清楚。不过,这一机制也有它自身的局限,并非一个天衣无缝的解决方案。

相关文章
相关标签/搜索