Java 中 Varargs 机制的理解 J2SE 1.5供给了“Varargs”机制。凭借这一机制,能够界说能和多个实参相匹配的形参。然后,能够用一种更简略的办法,来传递个数可变的实参。本文介绍这一机制的运用办法,以及这一机制与数组、泛型、重载之间的相互作用时的若干问题。到J2SE 1.4停止,一向无法在Java程序里界说实参个数可变的办法——由于Java要求实参(Arguments)和形参(Parameters)的数量和类 型都有必要逐个匹配,而形参的数目是在界说办法时就现已固定下来了。虽然能够经过重载机制,为同一个办法供给带有不同数量的形参的版别,可是这依然不能到达 让实参数量恣意改变的意图。可是,有些办法的语义要求它们有必要能承受个数可变的实参——例如闻名的main办法,就需求能承受一切的命令行参数为实参,而命令行参数的数目,事前底子无法断定下来。关于这个问题,传统上一般是选用“运用一个数组来包裹要传递的实参”的做法来敷衍。用数组包裹实参“用数组包裹实参”的做法能够分红三步:首要,为这个办法界说一个数组型的参数;然后在调用时,生成一个包含了一切要传递的实参的数组;最终,把这个数组作为一个实参传递曩昔。这种做法能够有用的到达“让办法能够承受个数可变的参数”的意图,仅仅调用时的办法不行简略。J2SE 1.5中供给了Varargs机制,答应直接界说能和多个实参相匹配的形参。然后,能够用一种更简略的办法,来传递个数可变的实参。Varargs的意义大体说来,“Varargs”是“variable number of arguments”的意思。有时分也被简略的称为“variable arguments”,不过由于这一种叫法没有阐明是什么东西可变,所以意义略微有点含糊。界说实参个数可变的办法只要在一个形参的“类型”与“参数名”之间加上三个接连的“.”(即“…”,英文里的句中省略号),就能够让它和不断定个实参相匹配。而一个带有这样的形参的办法,就是一个实参个数可变的办法。清单1:一个实参个数可变的办法留意,只要最终一个形参才干被界说成“能和不断定个实参相匹配”的。因而,一个办法里只能有一个这样的形参。别的,如果这个办法还有其它的形参,要把它们放到前面的方位上。编译器会在背地里把这最终一个形参转化为一个数组形参,并在编译出的class文件里作上一个记号,标明这是个实参个数可变的办法。清单2:实参个数可变的办法的隐秘形状由于存在着这样的转化,所以不能再为这个类界说一个和转化后的办法签名共同的办法。清单3:会导致编译过错的组合空白的存亡问题依据J2SE 1.5的语法,在“…”前面的空白字符是可有可无的。这样就有在“…”前面添加空白字符(形如“Object … args”)和在“…”前面不加空白字符(形如“Object… args”)的两种写法。由于现在和J2SE 1.5相合作的Java Code Conventions还没有正式发布,所以无法知道终究哪一种写法比较正统。不过,考虑到数组参数也有“Object [] args”和“Object[] args”两种书写办法,而正统的写法是不在“[]”前添加空白字符,好像采纳不加空白的“Object… args”的写法在全体上更和谐一些。调用实参个数可变的办法只要把要传递的实参逐个写到相应的方位上,就能够调用一个实参个数可变的办法。不需求其它的进程。清单4:能够传递若干个实参在背地里,编译器会把这种调用进程转化为用“数组包裹实参”的办法:清单5:悄悄呈现的数组创立别的,这儿说的“不断定个”也包含零个,所以这样的调用也是合乎情理的:清单6:也能够传递零个实参清单7:零实参对应空数组留意这时传递曩昔的是一个空数组,而不是null。这样就能够采纳共同的办法来处理,而不用检测究竟归于哪种状况。4. 处理个数可变的实参处理个数可变的实参的办法,和处理数组实参的办法根本相同。一切的实参,都被保存到一个和形参同名的数组里。依据实践的需求,把这个数组里的元素读出之后,要蒸要煮,就能够随意了。清单8:处理收到的实参们5. 转发个数可变的实参有时分,在承受了一组个数可变的实参之后,还要把它们传递给另一个实参个数可变的办法。由于编码时无法知道承受来的这一组实参的数目,所以“把它们 逐个写到该呈现的方位上去”的做法并不行行。不过,这并不意味着这是个不行完结的使命,由于还有别的一种办法,能够用来调用实参个数可变的办法。在J2SE 1.5的编译器的眼中,实参个数可变的办法是最终带了一个数组形参的办法的特例。因而,事前把整组要传递的实参放到一个数组里,然后把这个数组作为最终一个实参,传递给一个实参个数可变的办法,不会形成任何过错。凭借这一特性,就能够顺畅的完结转发了。清单9:转发收到的实参们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”的编译过错由于这一原因,不能在调用只支撑用数组包裹实参的办法的时分(例如在不是专门为J2SE 1.5规划第三方类库中留传的那些),直接选用这种简明的调用办法。如果不能修正本来的类,为要调用的办法添加参数个数可变的版别,而又想选用这种简明的调用办法,那么能够凭借“引进外加函数(Introduce Foreign Method)”和“引进本地扩展(Intoduce Local Extension)”的重构办法来近似的到达意图。7. 当个数可变的实参遇到泛型J2SE 1.5中新增了“泛型”的机制,能够在必定条件下把一个类型参数化。例如,能够在编写一个类的时分,把一个办法的形参的类型用一个标识符(如T)来代表, 至于这个标识符究竟表明什么类型,则在生成这个类的实例的时分再行指定。这一机制能够用来供给更充沛的代码重用和更严厉的编译时类型查看。不过泛型机制却不能和个数可变的形参合作运用。如果把一个能和不断定个实参相匹配的形参的类型,用一个标识符来代表,那么编译器会给出一个“generic array creation”的过错。清单11:当Varargs遇上泛型形成这个现象的原因在于J2SE 1.5中的泛型机制的一个内涵束缚——不能拿用标识符来代表的类型来创立这一类型的实例。在呈现支撑没有了这个束缚的Java版别之前,关于这个问题,根本没有太好的解决办法。不过,传统的“用数组包裹”的做法,并不受这个束缚的约束。清单12:能够编译的变通做法8. 重载中的挑选问题Java支撑“重载”的机制,答应在同一个类具有许多只要形参列表不同的办法。然后,由编译器依据调用时的实参来挑选究竟要履行哪一个办法。传统上的挑选,根本是按照“特别者优先”的准则来进行。一个办法的特别程度,取决于为了让它顺畅运转而需求满意的条件的数目,需求条件越多的越特别。在引进Varargs机制之后,这一准则依然适用,仅仅要考虑的问题丰厚了一些——传统上,一个重载办法的各个版别之中,只要形参数量与实参数量正 好共同的那些有被进一步考虑的资历。可是Varargs机制引进之后,彻底能够呈现两个版别都能匹配,在其它方面也别无二致,仅仅一个实参个数固定,而一 个实参个数可变的状况。遇到这种状况时,所用的断定规则是“实参个数固定的版别优先于实参个数可变的版别”。清单13:实参个数固定的版别优先如果在编译器看来,一起有多个办法具有相同的优先权,它就会堕入无法就究竟调用哪个办法作出一个挑选的状况。在这样的时分,它就会发作一个 “reference to 被调用的办法名 is ambiguous”的编译过错,并耐性的等候作了一些修正,足以革除它的利诱的新源代码的到来。在引进了Varargs机制之后,这种可能导致利诱的状况,又添加了一些。例如现在可能会有两个版别都能匹配,在其它方面也千篇一律,并且都是实参个数可变的抵触发作。清单14:左右都不是,为难了编译器别的,由于J2SE 1.5中有“Autoboxing/Auto-Unboxing”机制的存在,所以还可能发作两个版别都能匹配,并且都是实参个数可变,其它方面也如出一辙,仅仅一个能承受的实参是根本类型,而另一个能承受的实参是包裹类的抵触发作。清单15:Autoboxing/Auto-Unboxing带来的新问题9. 概括总结和“用数组包裹”的做法比较,真实的实参个数可变的办法,在调用时传递参数的操作更为简略,意义也更为清楚。不过,这一机制也有它本身的限制,并不是一个白璧无瑕的解决方案。 |