`
tonysmith
  • 浏览: 170376 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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代码
private static int sumUp(int... values) {    
}   
private static int sumUp(int... values) {
} 注意,只有最后一个形参才能被定义成“能和不确定个实参相匹配”的。因此,一个方法里只能有一个这样的形参。另外,如果这个方法还有其它的形参,要把它们放到前面的位置上。
编译器会在背地里把这最后一个形参转化为一个数组形参,并在编译出的class文件里作上一个记号,表明这是个实参个数可变的方法。
清单2:实参个数可变的方法的秘密形态
Java代码
private static int sumUp(int[] values) {    
}   
private static int sumUp(int[] values) {
} 由于存在着这样的转化,所以不能再为这个类定义一个和转化后的方法签名一致的方法。
清单3:会导致编译错误的组合
Java代码
private static int sumUp(int... values) {   
}   
  
private static int sumUp(int[] values) {   
}  
    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代码
private static int sumUp(int... values) {   
    int sum = 0;   
    for (int i = 0; i < values.length; i++) {   
        sum += values[i];   
    }   
    return sum;   
}  
    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代码
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);   
    }   
}  
    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代码
private static void testOverloading(int[] i) {   
    System.out.println("A");   
}   
  
public static void main(String[] args) {   
    testOverloading(1, 2, 3);// 编译出错   
}  
    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代码
private static <T> void testVarargs(T... args) {   
    // 编译出错   
}  
    private static <T> void testVarargs(T... args) {
        // 编译出错
    }
造成这个现象的原因在于J2SE 1.5中的泛型机制的一个内在约束——不能拿用标识符来代表的类型来创建这一类型的实例。在出现支持没有了这个约束的Java版本之前,对于这个问题,基本没有太好的解决办法。
不过,传统的“用数组包裹”的做法,并不受这个约束的限制。
清单12:可以编译的变通做法
Java代码
private static <T> void testVarargs(T[] args) {   
    for (int i = 0; i < args.length; i++) {   
        System.out.println(args[i]);   
    }   
}  
    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代码
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");   
    }   
}  
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代码
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) {   
    }   
}  
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代码
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) {   
    }   
}  
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. 归纳总结
和“用数组包裹”的做法相比,真正的实参个数可变的方法,在调用时传递参数的操作更为简单,含义也更为清楚。不过,这一机制也有它自身的局限,并不是一个完美无缺的解决方案。
参考资源
可以通过Sun的Java Technology页面找到下载J2SE 1.5的SDK及其文档的链接,目前的最新版本是J2SDK 5.0 Beta 2。 《JSR 201: Extending the Java Programming Language with Enumerations, Autoboxing, Enhanced for loops and Static Import》中定义了很多在J2SE 1.5中出现了的新语言特性。虽然在标题里并没有明确提到,但是Varargs机制也在其中。 Calvin Austin在《J2SE 1.5 in a Nutshell》一文中,比较全面的对J2SE 1.5的新特性进行了简单的介绍。 John Zukowski在《驯服 Tiger:Tiger 预览版现已推出》一文中,介绍了如何开始使用J2SDK 1.5的基础知识。不过因为这篇文章是依照J2SDK 1.5 Alpha版的状况所写,所以里面提到的一些细节(如下载地址和默认安装路径)已经发生了变化。 John Zukowski在《驯服 Tiger:格式化输出》一文中,介绍了利用J2SE 1.5中新增的功能进行字符串格式化的办法。 Gilad Bracha在《Generics in the Java Programming Language》一文中,细致的介绍了J2SE 1.5中的泛型机制的使用方法和各种限制。 Martin Fowler在《Refactoring -- Improving the Design of Existing Code》(中译本名为《重构——改善既有代码的设计》, 由侯捷、熊节合译)一书的第七章《在对象之间移动特性(Moving Features Between Objects)》中,介绍了被称作“引入外加函数(Introduce Foreign Method)”和“引入本地扩展(Intoduce Local Extension)”的重构手法。 Gilad Bracha、James Gosling、Bill Joy和Guy Steele在《The Java Language Specification, 2nd Edition》一书里的《Classes》一章中的《Overloading》一节和《Expression》一章中的《Method Invocation Expressions》一节里深入的讨论了重载方面的问题。不过因为这本书是依照J2SDK 1.2版的状况所写,所以并没有完整地涵盖J2SE 1.5中的实际情况。

分享到:
评论

相关推荐

    Java实现多变行游戏

    描述: 多边形游戏是一个单人玩的游戏,开始时有一个由n个顶点构成的多边形。每个顶点被赋予一个整数值,每条边被赋予一个运算符“+”或“*”。所有边依次用整数从1到n编号。 游戏第1步,将一条边删除。...

    java读取excel数据!

    java读取excel表格的数据,并将其保存!已经调试过了,可以使用!

    带有不完全信息随机截尾试验下伽玛分布多变点模型的参数估计

    利用MCMC方法研究了带有不完全信息随机截尾试验下伽玛分布多变点模型的参数估计问题.通过添加缺损的寿命变量数据得到了瑞利分布的完全数据似然函数,对各参数的满条件分布进行了随机抽样.随机模拟证实了各参数估计的...

    截断删失数据下瑞利分布多变点模型的贝叶斯估计

    为研究截断删失数据下瑞利分布多变点模型的参数估计问题,利用MCMC方法,通过筛选法添加部分缺损的寿命变量数据,得到了相对简单的似然函数.在获得变点位置和其它参数的满条件分布后,利用Gibbs抽样和Metropolis-...

    多变流体动力涡旋的边界稳定共振模

    多变流体动涡描述了一个有效的(2 + 1)维声学时空,内反射边界为r = rc。 该物理系统像旋转的Kerr黑洞一样,具有半径为re的人体工学区域和半径为rs的内部非点状曲率奇点。 有趣的是,表征有效几何形状的基本比率re ...

    JAVA 范例大全 光盘 资源

    实例95 参数不确定(可变长参数) 249 实例96 方法改变(协变式返回类型) 251 实例97 静态导入 252 实例98 动物搭配(泛型) 253 实例99 人员信息(枚举类型) 256 实例100 printf()用法 260 实例101 使用...

    面向对象的软件工程:构建复杂且多变的系统

    面向对象的软件工程:构建复杂且多变的系统

    全息多变重力模型

    本文报道了通过新的全息多变暗能量重建f(T)引力引起的宇宙学研究。 我们假设两种方法,即哈勃参数H的特定形式和f(T)的解。 我们从两种情况下的总密度和压力中获得了减速参数和有效状态方程以及状态参数的扭转...

    面向对象的软件工程-构建复杂且多变的系统

    面向对象的软件工程-构建复杂且多变的系统----面向对象的软件工程-构建复杂且多变的系统

    如何学好JAVA

    不幸的是因为要学的东西太多且多变,没时间陪老婆家人或女朋友,导致身心疲惫,严重者甚至导致抑郁症。幸运的是别人要抢你饭碗绝非易事,他们或她们需要付出很多才能达成心愿。  JAVA不要孤立地去学习,需要综合...

    多变元微积分(pdf格式)

    多变元微积分(pdf格式),非常生动,帮助你学习微积分。

    多变的天气(地理)教案.pdf

    多变的天气(地理)教案.pdf

    46道java基础知识面试题详解含答案(值得珍藏)

    在Java面试过程中,熟练掌握Java...同时,在实际工作中,遇到的问题往往复杂多变,熟练掌握Java基础知识能够帮助求职者更好地分析问题、寻找解决方案并实现代码。 最后,熟练掌握Java基础知识能够提高求职者的竞争力。

    Palatini重力中的多变星

    我们在Palatini重力中为Starobinsky模型导出了改进的Lane-Emden方程,该方程可在数值上求解。 将结果与广义相对论提供的结果进行比较,我们发现根据$$ MR $$ &lt;math&gt; &lt;mrow&gt; &lt;mi&gt; M &lt;/ mi&gt; &lt;mo&gt;-&lt;/ mo&gt; &lt; mi&gt; ...

    高一政治多变的价格课件新人教版.ppt

    高一政治多变的价格课件新人教版.ppt

    中班健康教案及说课稿《多变的纸箱》润新教育.txt

    中班健康教案及说课稿《多变的纸箱》润新教育.txt

    3.1 多变的天气 课件 (共38张PPT).pptx

    初中地理人教版七年级第三章第一节多变的天气课件PPT

    matlab开发-多变量子空间识别moesp

    matlab开发-多变量子空间识别moesp。使用moesp算法进行子空间识别的工具。

    sql多变查询举例 语句

    左连接、右连接、内连接、外连接。左连接、右连接、内连接、外连接。左连接、右连接、内连接、外连接。左连接、右连接、内连接、外连接。左连接、右连接、内连接、外连接。

Global site tag (gtag.js) - Google Analytics