到J2SE1.4為止,一直無法在Java程式裡定義實參個數可變的方法-因為Java要求實參(Arguments)和形參(Parameters)的數量和類型都必須逐一匹配,而形參的數目是在定義方法時就已經固定下來了。雖然可以透過重載機制,為同一個方法提供不同數量的形參的版本,但這仍然不能達到讓實參數量任意變化的目的。
然而,有些方法的語意要求它們必須能接受個數可變的實參-例如著名的main方法,就需要能接受所有的命令列參數為實參,而命令行參數的數目,事先根本無法確定。
對於這個問題,傳統上一般是採用「利用一個陣列來包裹要傳遞的實參」的做法來應付。本文主要介紹了Java中可變長度參數代碼詳解,涉及了實參個數可變的定義方法,數組包裹實參等幾個問題,具有一定參考價值,需要的朋友可以了解下。
1.用陣列包裹實參
「用陣列包裹實參」的做法可以分成三步驟:首先,為這個方法定義一個陣列型的參數;然後在呼叫時,產生一個包含了所有要傳遞的實參的陣列;最後,把這個陣列當作一個實參傳遞過去。
這種做法可以有效的達到「讓方法可以接受個數可變的參數」的目的,只是呼叫時的形式不夠簡單。
J2SE1.5中提供了Varargs機制,允許直接定義能和多個實參相匹配的形參。從而,可以用更簡單的方式,來傳遞個數可變的實參。
Varargs的意思
大致說來,「Varargs」是「variablenumberofarguments」的意思。有時候也被簡單的稱為“variablearguments”,不過因為這一種叫法沒有說明是什麼東西可變,所以意義稍微有點模糊。
2.定義實參數可變的方法
#只要在一個形參的「型別」與「參數名稱」之間加上三個連續的“.”(即“...”,英文裡的句中省略號),就可以讓它和不確定個實參相匹配。而一個帶有這樣的形參的方法,就是一個實參個數可變的方法。
清單1:一個實參數可變的方法
private static int sumUp(int... values) { }
注意,只有最後一個形參才能被定義成「能和不確定個實參相匹配」的。因此,一個方法裡只能有一個這樣的形參。另外,如果這個方法還有其它的形參,要把它們放到前面的位置。
編譯器會在背地裡把這最後一個形參轉換為一個陣列形參,並在編譯的class檔案裡作上一個記號,表示這是個實參個數可變的方法。
清單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(newint[]{1,2 ,3,4});
另外,這裡說的「不確定個」也包含零個,所以這樣的呼叫也是合乎情理的:
#清單6:也可以傳遞零個實參
sumUp();
這種呼叫方法被編譯器秘密轉換後的效果,則等同於這樣:
清單7:零實參對應空數組
sumUp(newint[]{});
注意這時傳遞過去的是空數組,而不是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.轉發個數可變的實參
#有时候,在接受了一组个数可变的实参之后,还要把它们传递给另一个实参个数可变的方法。因为编码时无法知道接受来的这一组实参的数目,所以“把它们逐一写到该出现的位置上去”的做法并不可行。不过,这并不意味着这是个不可完成的任务,因为还有另外一种办法,可以用来调用实参个数可变的方法。
在J2SE1.5的编译器的眼中,实参个数可变的方法是最后带了一个数组形参的方法的特例。因此,事先把整组要传递的实参放到一个数组里,然后把这个数组作为最后一个实参,传递给一个实参个数可变的方法,不会造成任何错误。借助这一特性,就可以顺利的完成转发了。
清单9:转发收到的实参们
public class PrintfSample { public static void main(String[] args) { printOut("Pi:%f E:%f\n", Math.PI, Math.E); } private static void printOut(String format, Object... args) { System.out.printf(format, args); } }
6.是数组?不是数组?
尽管在背地里,编译器会把能匹配不确定个实参的形参,转化为数组形参;而且也可以用数组包了实参,再传递给实参个数可变的方法;但是,这并不表示“能匹配不确定个实参的形参”和“数组形参”完全没有差异。
一个明显的差异是,如果按照调用实参个数可变的方法的形式,来调用一个最后一个形参是数组形参的方法,只会导致一个“cannotbeappliedto”的编译错误。
清单10:一个“cannotbeappliedto”的编译错误
private static void testOverloading(int[] i) { System.out.println("A"); } public static void main(String[] args) { testOverloading(1, 2, 3);//编译出错 }
由于这一原因,不能在调用只支持用数组包裹实参的方法的时候(例如在不是专门为J2SE1.5设计第三方类库中遗留的那些),直接采用这种简明的调用方式。
如果不能修改原来的类,为要调用的方法增加参数个数可变的版本,而又想采用这种简明的调用方式,那么可以借助“引入外加函数(IntroduceForeignMethod)”和“引入本地扩展(IntoduceLocalExtension)”的重构手法来近似的达到目的。
7.当个数可变的实参遇到泛型
J2SE1.5中新增了“泛型”的机制,可以在一定条件下把一个类型参数化。例如,可以在编写一个类的时候,把一个方法的形参的类型用一个标识符(如T)来代表,至于这个标识符到底表示什么类型,则在生成这个类的实例的时候再行指定。这一机制可以用来提供更充分的代码重用和更严格的编译时类型检查。
不过泛型机制却不能和个数可变的形参配合使用。如果把一个能和不确定个实参相匹配的形参的类型,用一个标识符来代表,那么编译器会给出一个“genericarraycreation”的错误。
清单11:当Varargs遇上泛型
private static void testVarargs(T... args) {//编译出错 }
造成这个现象的原因在于J2SE1.5中的泛型机制的一个内在约束——不能拿用标识符来代表的类型来创建这一类型的实例。在出现支持没有了这个约束的Java版本之前,对于这个问题,基本没有太好的解决办法。
不过,传统的“用数组包裹”的做法,并不受这个约束的限制。
清单12:可以编译的变通做法
private static void testVarargs(T[] args) { for (int i = 0; i < args.length; i++) { System.out.println(args[i]); } }
8.重载中的选择问题
Java支持“重载”的机制,允许在同一个类拥有许多只有形参列表不同的方法。然后,由编译器根据调用时的实参来选择到底要执行哪一个方法。
传统上的选择,基本是依照“特殊者优先”的原则来进行。一个方法的特殊程度,取决于为了让它顺利运行而需要满足的条件的数目,需要条件越多的越特殊。
在引入Varargs机制之后,这一原则仍然适用,只是要考虑的问题丰富了一些——传统上,一个重载方法的各个版本之中,只有形参数量与实参数量正好一致的那些有被进一步考虑的资格。但是Varargs机制引入之后,完全可以出现两个版本都能匹配,在其它方面也别无二致,只是一个实参个数固定,而一个实参个数可变的情况。
遇到这种情况时,所用的判定规则是“实参个数固定的版本优先于实参个数可变的版本”。
清单13:实参个数固定的版本优先
如果在编译器看来,同时有多个方法具有相同的优先权,它就会陷入无法就到底调用哪个方法作出一个选择的状态。在这样的时候,它就会产生一个“referenceto被调用的方法名isambiguous”的编译错误,并耐心的等候作了一些修改,足以免除它的迷惑的新源代码的到来。
在引入了Varargs机制之后,这种可能导致迷惑的情况,又增加了一些。例如现在可能会有两个版本都能匹配,在其它方面也如出一辙,而且都是实参个数可变的冲突发生。
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"); } }
如果在编译器看来,同时有多个方法具有相同的优先权,它就会陷入无法就到底调用哪个方法作出一个选择的状态。在这样的时候,它就会产生一个“referenceto被调用的方法名isambiguous”的编译错误,并耐心的等候作了一些修改,足以免除它的迷惑的新源代码的到来。
在引入了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) { } }
另外,因为J2SE1.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.归纳总结
和“用数组包裹”的做法相比,真正的实参个数可变的方法,在调用时传递参数的操作更为简单,含义也更为清楚。不过,这一机制也有它自身的局限,并不是一个完美无缺的解决方案。
以上内容就是关于Java中可变长度参数代码详解的全部内容,希望能帮助到大家。
相关推荐:
以上是Java中可變長度參數程式碼詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!