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. 概括總結

和「用數組包裹」的作法相比,真正的實參個數可變的方法,在調用時傳遞參數的操做更爲簡單,含義也更爲清楚。不過,這一機制也有它自身的侷限,並非一個天衣無縫的解決方案。

相關文章
相關標籤/搜索