`

J2SE5中可变数量形参Varargs的使用

    博客分类:
  • java
阅读更多

J2SE 1.5提供了“Varargs”机制。借助这一机制,可以定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参。本文介绍 这一机制的使用方法,以及这一机制与数组、泛型、重载之间的相互作用时的若干问题。
到J2SE 1.4为止,一直无法在Java程序里定义实参个数可变的方法——因为Java要求实参(Arguments)和形参(Parameters)的数量和类 型都必须逐一匹配,而形参的数目是在定义方法时就已经固定下来了。尽管可以通过重载机制,为同一个方法提供带有不同数量的形参的版本,但是这仍然不能达到 让实参数量任意变化的目的。

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

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

1. 用数组包裹实参
“用数组包裹实参”的做法可以分成三步:首先,为这个方法定义一个数组型的参数;然后在调 用时,生成一个包含了所有要传递的实参的数组;最后,把这个数组作为一个实参传递过去。

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

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

Varargs 的含义
大体说来,“Varargs”是“variable number of arguments”的意思。有时候也被简单的称为“variable arguments”,不过因为这一种叫法没有说明是什么东西可变,所以意义稍微有点模糊。

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

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

Java代码 复制代码
  1. private   static   int  sumUp( int ... values) {    
  2. }   
private static int sumUp(int... values) { 
} 

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

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

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

Java代码 复制代码
  1. private   static   int  sumUp( int [] values) {    
  2. }   
private static int sumUp(int[] values) { 
} 

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

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

Java代码 复制代码
  1. private   static   int  sumUp( int ... values) {   
  2. }   
  3.   
  4. private   static   int  sumUp( int [] values) {   
  5. }  
    private static int sumUp(int... values) {
    }

    private static int sumUp(int[] values) {
    }


空白的存亡问题
根据J2SE 1.5的语法,在“...”前面的空白字符是可有可无的。这样就有在“...”前面添加空白字符(形如“Object ... args”)和在“...”前面不加空白字符(形如“Object... args”)的两种写法。因为目前和J2SE 1.5相配合的Java Code Conventions还没有正式发布,所以无法知道究竟哪一种写法比较正统。不过,考虑到数组参数也有“Object [] args”和“Object[] args”两种书写方式,而正统的写法是不在“[]”前添加空白字符,似乎采取不加空白的“Object... args”的写法在整体上更协调一些。

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

清单4:可以传递若干个实参
sumUp(1, 3, 5, 7);
在背 地里,编译器会把这种调用过程转化为用“数组包裹实参”的形式:

清单5:偷偷出现的数组创建
sumUp(new int[]{1, 2, 3, 4});
另外,这里说的“不确定个”也包括零个,所以这样的调用也是合乎情理的:

清单6:也 可以传递零个实参
sumUp();
这种调用方法被编译器秘密转化之后的效果,则等同于这样:

清单7:零实参对应空 数组
sumUp(new int[]{});
注意这时传递过去的是一个空数组,而不是null。这样就可以采取统一的形式来处理,而 不必检测到底属于哪种情况。

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

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

Java代码 复制代码
  1. private   static   int  sumUp( int ... values) {   
  2.      int  sum =  0 ;   
  3.      for  ( int  i =  0 ; i < values.length; i++) {   
  4.         sum += values[i];   
  5.     }   
  6.      return  sum;   
  7. }  
    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:转发收到的实参们

Java代码 复制代码
  1. public   class  PrintfSample {   
  2.   
  3.      public   static   void  main(String[] args) {   
  4.          // 打印出“Pi:3.141593 E:2.718282”   
  5.         printOut( "Pi:%f E:%f\n" , Math.PI, Math.E);   
  6.     }   
  7.   
  8.      private   static   void  printOut(String format, Object... args) {   
  9.          // J2SE 1.5里PrintStream新增的 printf(String format, Object... args)方法   
  10.         System.out.printf(format, args);   
  11.     }   
  12. }  
    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);
        }
    }


Java里的“printf”和“sprintf”
C语言里的printf(按一定的格式输出字符串)和sprintf(按一定 的格式组合字符串)是十分经典的使用Varargs机制的例子。在 J2SE 1.5中,也分别在java.io.PrintStream类和java.lang.String类中提供了类似的功能。

按一定的格式 输出字符串的功能,可以通过调用PrintStream对象的printf(String format, Object... args)方法来实现。

按一定的格式组合字符串的工作,则可以通过调用String类的String format(String format, Object... args)静态方法来进行。

6. 是数组?不是数组?
尽管在背地里,编译器会把能匹配不确定个实参的形 参,转化为数组形参;而且也可以用数组包了实参,再传递给实参个数可变的方法;但是,这并不表示“能匹配不确定个实参的形参”和“数组形参”完全没有差 异。

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

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

Java代码 复制代码
  1. private   static   void  testOverloading( int [] i) {   
  2.     System.out.println( "A" );   
  3. }   
  4.   
  5. public   static   void  main(String[] args) {   
  6.     testOverloading( 1 2 3 ); // 编译出错   
  7. }  
    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遇上泛型

Java代码 复制代码
  1. private   static  <T>  void  testVarargs(T... args) {   
  2.      // 编译出错   
  3. }  
    private static <T> void testVarargs(T... args) {
        // 编译出错
    }

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

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

清单12:可以编译的变通做法

Java代码 复制代码
  1. private   static  <T>  void  testVarargs(T[] args) {   
  2.      for  ( int  i =  0 ; i < args.length; i++) {   
  3.         System.out.println(args[i]);   
  4.     }   
  5. }  
    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:实参个数固定的版本优先

Java代码 复制代码
  1. public   class  OverloadingSampleA {   
  2.   
  3.      public   static   void  main(String[] args) {   
  4.         testOverloading( 1 ); // 打印出A   
  5.         testOverloading( 1 2 ); // 打印出B   
  6.         testOverloading( 1 2 3 ); // 打印出C   
  7.     }   
  8.   
  9.      private   static   void  testOverloading( int  i) {   
  10.         System.out.println( "A" );   
  11.     }   
  12.   
  13.      private   static   void  testOverloading( int  i,  int  j) {   
  14.         System.out.println( "B" );   
  15.     }   
  16.   
  17.      private   static   void  testOverloading( int  i,  int ... more) {   
  18.         System.out.println( "C" );   
  19.     }   
  20. }  
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:左右都不是,为难了编译器

Java代码 复制代码
  1. public   class  OverloadingSampleB {   
  2.   
  3.      public   static   void  main(String[] args) {   
  4.         testOverloading( 1 2 3 ); // 编译出错   
  5.     }   
  6.   
  7.      private   static   void  testOverloading(Object... args) {   
  8.     }   
  9.   
  10.      private   static   void  testOverloading(Object o, Object... args) {   
  11.     }   
  12. }  
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带来的新问题

Java代码 复制代码
  1. public   class  OverloadingSampleC {   
  2.   
  3.      public   static   void  main(String[] args) {  /* 编译出错 */   
  4.         testOverloading( 1 2 );  /* 还是编译出错 */   
  5.         testOverloading( new  Integer( 1 ),  new  Integer( 2 ));   
  6.     }   
  7.   
  8.      private   static   void  testOverloading( int ... args) {   
  9.     }   
  10.   
  11.      private   static   void  testOverloading(Integer... args) {   
  12.     }   
  13. }  
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. 归纳总结
和“用数组包裹”的做法相比,真正的实参个数可变的方法,在调用时传递参数的操作更为简单,含义也更为清楚。不 过,这一机制也有它自身的局限,并不是一个完美无缺的解决方案。

分享到:
评论

相关推荐

    j2se1.5中文api

    J2SE 1.5允许在方法签名中使用省略号(...),创建可变参数的方法。这使得方法可以接受任意数量的相同类型的参数,如public void printNumbers(int... nums)。 七、静态导入(Static Import) 静态导入允许将类的...

    j2se5.rar_J2SE 架构_j2se

    6. **变量推断(varargs)**:允许方法参数接受可变数量的参数,语法上用三个点(...)表示,简化了方法调用。 7. **静态导入**:允许一次性导入某个包中的所有静态成员,避免了多次import语句,使代码更易读。 8....

    一套全面的j2se5学习课件

    5. **可变参数(Varargs)**:J2SE5引入了可变参数,允许一个方法接受不定数量的参数,通过`...`语法实现,提高了代码的灵活性。 6. **增强的for循环(Enhanced For Loop)**:也被称为foreach循环,使得遍历数组和...

    J2SE5参考大全

    本书《J2SE5参考大全》开篇即介绍了Java的起源,强调了Java如何成为一门重要的编程语言。通过回顾历史,我们可以理解Java是如何在不断变化的技术环境中脱颖而出的。 #### 2. 计算机语言的发展脉络 在讨论Java之前...

    自做的J2SE5中文版 API part5

    自己做的J2SE5中文版的API,全是SUN那DOWN来的,自己做成了CHM格式。。。

    j2se6中文网页版

    9. **异常处理**:Java的异常处理机制在J2SE 6中得到进一步完善,包括检查型异常和运行时异常的分类,以及try-catch-finally语句块的使用。 10. **垃圾回收与内存管理**:J2SE 6改进了垃圾回收算法,提升了内存管理...

    J2SE6.0 documentation 中文~

    2. **语言增强**:Java SE 6.0引入了一些语言层面的改进,如增强的for循环(foreach)、自动装箱/拆箱、可变参数(varargs)等,这些改进提高了代码的可读性和简洁性。 3. **集合框架**:Java 6对集合框架进行了...

    J2SE5 API开发文档

    这是j2se5 api的中文版,是.chm格式,搜索方法和类时更加方便。希望对大家有帮助。关于英文版,因太大无法上传,大家可去官方下.html格式的,要下.chm格式的只能去别处看一下了!

    Java:The Complete Reference,J2SE 5 Edition

    中文译名是J2SE参考大全(第5版) 这份是自己制作的电子书,现在还没有制作完成,因为内容太多,关打字就打个半死.

    j2se中文api.rar

    Java 2 Standard Edition (J2SE) 是 Java 平台的核心部分,主要为桌面应用程序提供运行环境。这个压缩包 "j2se中文api.rar" ...压缩包中的文档详细解释了每个类、接口和方法的使用,是学习和开发过程中不可或缺的工具。

    j2se5_api_zh

    4. **可变参数(Varargs)**:可变参数允许方法接收一个或多个同类型的参数,这使得方法调用更灵活,尤其是在处理数组时。 5. **注解(Annotations)**:注解是一种元数据,用于提供编译器和运行时系统使用的元信息...

    J2SE 5 中文API 简化版

    J2SE 5 中文API 简化版 保留lang,io,util几个主要包的说明文档

    SCJP Exam for J2SE 5

    7. **可变参数(Varargs)**:允许多个相同类型的参数,用三个点(...)表示,如`public void method(Type... args)`。 8. **类型安全的异常检查**:Java 5引入了断言(assert),用于测试代码的正确性,同时改进了...

    J2SE 5EDdition.part5

    J2SE 5EDdition.part5 J2SE 5EDdition.part5

    J2SE 中文帮助文档

    5. **输入/输出(I/O)**:包括文件操作、流的使用,以及序列化和反序列化。 6. **多线程**:理解线程的概念,创建和管理线程,以及同步和互斥机制。 7. **网络编程**:使用Socket进行客户端和服务器端通信,理解...

    j2se中文6.0api

    Java 2 Platform, Standard Edition (J2SE) 是 Java 技术的核心部分,它提供了用于开发和部署桌面应用、服务器端应用以及嵌入式系统的工具和API。J2SE 6.0 版本是其历史上的一个重要里程碑,带来了许多性能优化、新...

    J2SE6.0中文 API

    本文档是 JavaTM Platform Standard Edition 6 的 API 规范。

    Java中的可变参数常见用法实例总结

    在J2SE 1.4之前,Java中无法定义实参个数可变的方法,因为Java要求实参和形参的数量和类型都必须逐一匹配。然而,有些方法的语义要求它们必须能接受个数可变的实参,例如main方法,就需要能接受所有的命令行参数为...

    J2SE 5.0 API (中文版)

    泛型是 J2SE 5.0 中最重要的特性之一,允许在类、接口和方法中使用类型参数。这样可以确保在编译时期就进行类型检查,减少了运行时的类型转换,并提高了代码的可读性和安全性。 2. **枚举(Enums)** 之前,Java ...

    j2se_中文版

    中文版的J2SE API文档对于中国开发者来说尤其方便,它提供了全面的API参考,帮助理解并使用Java编程语言的核心库。 在Java编程中,J2SE是基础,它包含了核心类库,如集合框架、I/O流、网络编程、多线程、GUI(图形...

Global site tag (gtag.js) - Google Analytics