在 Java 5 中提供了變長參數(shù),允許在調(diào)用方法時(shí)傳入不定長度的參數(shù)。變長參數(shù)是 Java 的一個(gè)語法糖,本質(zhì)上還是基于數(shù)組的實(shí)現(xiàn):
void foo(String... args); void foo(String[] args);
//方法簽名 ([Ljava/lang/String;)V // public void foo(String[] args)
定義方法
在定義方法時(shí),在最后一個(gè)形參后加上三點(diǎn) …,就表示該形參可以接受多個(gè)參數(shù)值,多個(gè)參數(shù)值被當(dāng)成數(shù)組傳入。上述定義有幾個(gè)要點(diǎn)需要注意:
-
可變參數(shù)只能作為函數(shù)的最后一個(gè)參數(shù),但其前面可以有也可以沒有任何其他參數(shù)
-
由于可變參數(shù)必須是最后一個(gè)參數(shù),所以一個(gè)函數(shù)最多只能有一個(gè)可變參數(shù)
-
Java的可變參數(shù),會(huì)被編譯器轉(zhuǎn)型為一個(gè)數(shù)組
-
變長參數(shù)在編譯為字節(jié)碼后,在方法簽名中就是以數(shù)組形態(tài)出現(xiàn)的。這兩個(gè)方法的簽名是一致的,不能作為方法的重載。如果同時(shí)出現(xiàn),是不能編譯通過的??勺儏?shù)可以兼容數(shù)組,反之則不成立
public void foo(String...varargs){} foo("arg1", "arg2", "arg3"); //上述過程和下面的調(diào)用是等價(jià)的 foo(new String[]{"arg1", "arg2", "arg3"});
-
J2SE 1.5 中新增了“泛型”的機(jī)制,可以在一定條件下把一個(gè)類型參數(shù)化。例如,可以在編寫一個(gè)類的時(shí)候,把一個(gè)方法的形參的類型用一個(gè)標(biāo)識(shí)符(如T)來代表, 至于這個(gè)標(biāo)識(shí)符到底表示什么類型,則在生成這個(gè)類的實(shí)例的時(shí)候再行指定。這一機(jī)制可以用來提供更充分的代碼重用和更嚴(yán)格的編譯時(shí)類型檢查。不過泛型機(jī)制卻不能和個(gè)數(shù)可變的形參配合使用。如果把一個(gè)能和不確定個(gè)實(shí)參相匹配的形參的類型,用一個(gè)標(biāo)識(shí)符來代表,那么編譯器會(huì)給出一個(gè) “generic array creation” 的錯(cuò)誤
public class Varargs { public static void test(String... args) { for(String arg : args) {//當(dāng)作數(shù)組用foreach遍歷 System.out.println(arg); } } //Compile error //The variable argument type Object of the method must be the last parameter //public void error1(String... args, Object o) {} //public void error2(String... args, Integer... i) {} //Compile error //Duplicate method test(String...) in type Varargs //public void test(String[] args){} }
可變參數(shù)方法的調(diào)用
調(diào)用可變參數(shù)方法,可以給出零到任意多個(gè)參數(shù),編譯器會(huì)將可變參數(shù)轉(zhuǎn)化為一個(gè)數(shù)組。也可以直接傳遞一個(gè)數(shù)組,示例如下:
public class Varargs { public static void test(String... args) { for(String arg : args) { System.out.println(arg); } } public static void main(String[] args) { test();//0個(gè)參數(shù) test("a");//1個(gè)參數(shù) test("a","b");//多個(gè)參數(shù) test(new String[] {"a", "b", "c"});//直接傳遞數(shù)組 } }
方法重載
優(yōu)先匹配固定參數(shù)
調(diào)用一個(gè)被重載的方法時(shí),如果此調(diào)用既能夠和固定參數(shù)的重載方法匹配,也能夠與可變長參數(shù)的重載方法匹配,則選擇固定參數(shù)的方法:
public class Varargs { public static void test(String... args) { System.out.println("version 1"); } public static void test(String arg1, String arg2) { System.out.println("version 2"); } public static void main(String[] args) { test("a","b");//version 2 優(yōu)先匹配固定參數(shù)的重載方法 test();//version 1 } }
>匹配多個(gè)可變參數(shù)
調(diào)用一個(gè)被重載的方法時(shí),如果此調(diào)用既能夠和兩個(gè)可變長參數(shù)的重載方法匹配,則編譯出錯(cuò):
public class Varargs { public static void test(String... args) { System.out.println("version 1"); } public static void test(String arg1, String... arg2) { System.out.println("version 2"); } public static void main(String[] args) { test("a","b");//Compile error } }
方法重寫
避免帶有變長參數(shù)的方法重載
即便編譯器可以按照優(yōu)先匹配固定參數(shù)的方式確定具體的調(diào)用方法,但在閱讀代碼的依然容易掉入陷阱。要慎重考慮變長參數(shù)的方法重載。
別讓 null 值和空值威脅到變長方法
public class Client { public void methodA(String str,Integer... is){ } public void methodA(String str,String... strs){ } public static void main(String[] args) { Client client = new Client(); client.methodA("China", 0); client.methodA("China", "People"); client.methodA("China"); //compile error client.methodA("China",null); //compile error } }
修改如下:
public static void main(String[] args) { Client client = new Client(); String[] strs = null; client.methodA("China",strs); }
讓編譯器知道這個(gè)null值是String類型的,編譯即可順利通過,也就減少了錯(cuò)誤的發(fā)生。
覆寫變長方法也要循規(guī)蹈矩
package com; public class VarArgsTest2 { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub // 向上轉(zhuǎn)型 Base base = new Sub(); base.print("hello"); // 不轉(zhuǎn)型 Sub sub = new Sub(); sub.print("hello");//compile error } } // 基類 class Base { void print(String... args) { System.out.println("Base......test"); } } // 子類,覆寫父類方法 class Sub extends Base { @Override void print(String[] args) { System.out.println("Sub......test"); } }
第一個(gè)能編譯通過,這是為什么呢?事實(shí)上,base 對象把子類對象 sub 做了向上轉(zhuǎn)型,形參列表是由父類決定的,當(dāng)然能通過。而看看子類直接調(diào)用的情況,這時(shí)編譯器看到子類覆寫了父類的 print 方法,因此肯定使用子類重新定義的 print 方法,盡管參數(shù)列表不匹配也不會(huì)跑到父類再去匹配下,因?yàn)檎业搅司筒辉僬伊?,因此有了類型不匹配的錯(cuò)誤。
這是個(gè)特例,覆寫的方法參數(shù)列表竟然可以與父類不相同,這違背了覆寫的定義,并且會(huì)引發(fā)莫名其妙的錯(cuò)誤。
這里,總結(jié)下覆寫必須滿足的條件:
-
覆寫方法不能縮小訪問權(quán)限
-
參數(shù)列表必須與被覆寫方法相同(包括顯示形式)
-
返回類型必須與被覆寫方法的相同或是其子類
-
覆寫方法不能拋出新的異常,或者超出父類范圍的異常,但是可以拋出更少、更有限的異常,或者不拋出異常
可能出現(xiàn)的問題
使用 Object… 作為變長參數(shù):
public void foo(Object... args) { System.out.println(args.length); } foo(new String[]{"arg1", "arg2", "arg3"}); //3 foo(100, new String[]{"arg1", "arg1"}); //2 foo(new Integer[]{1, 2, 3}); //3 foo(100, new Integer[]{1, 2, 3}); //2 foo(1, 2, 3); //3 foo(new int[]{1, 2, 3}); //1
int[] 無法轉(zhuǎn)型為 Object[], 因而被當(dāng)作一個(gè)單純的數(shù)組對象 ; Integer[] 可以轉(zhuǎn)型為 Object[], 可以作為一個(gè)對象數(shù)組。
反射方法調(diào)用時(shí)的注意事項(xiàng)
public class Test { public static void foo(String... varargs){ System.out.println(args.length); } public static void main(String[] args){ String[] varArgs = new String[]{"arg1", "arg2"}; try{ Method method = Test.class.getMethod("foo", String[].class); method.invoke(null, varArgs); method.invoke(null, (Object[])varArgs); method.invoke(null, (Object)varArgs); method.invoke(null, new Object[]{varArgs}); } catch (Exception e){ e.printStackTrace(); } } }
上面的四個(gè)調(diào)用中,前兩個(gè)都會(huì)在運(yùn)行時(shí)拋出 java.lang.IllegalArgumentException: wrong number of arguments 異常,后兩個(gè)則正常調(diào)用。
反射是運(yùn)行時(shí)獲取的,在運(yùn)行時(shí)看來,可變長參數(shù)和數(shù)組是一致的,因而方法簽名為:
//方法簽名 ([Ljava/lang/String;)V // public void foo(String[] varargs)
再來看一下 Method 對象的方法聲明:
Object invoke(Object obj, Object... args)
args 雖然是一個(gè)可變長度的參數(shù),但是 args 的長度是受限于該方法對象代表的真實(shí)方法的參數(shù)列表長度的,而從運(yùn)行時(shí)簽名來看,([Ljava/lang/String;)V 實(shí)際上只有一個(gè)形參,即 String[] varargs,因而 invoke(Object obj, Object… args) 中可變參數(shù) args 的實(shí)參長度只能為1
//Object invoke(Object obj, Object... args) //String[] varArgs = new String[]{"arg1", "arg2"}; method.invoke(null, varArgs); //varArgs長度為2,錯(cuò)誤 method.invoke(null, (Object[])varArgs); //將String[]轉(zhuǎn)換為Object[],長度為2的,錯(cuò)誤 method.invoke(null, (Object)varArgs);//將整個(gè)String[] 轉(zhuǎn)為Object,長度為1,符合 method.invoke(null, new Object[]{varArgs});//Object[]長度為1,正確。上一個(gè)和這個(gè)是等價(jià)的
什么時(shí)候使用可變長參數(shù)?
Stack Overflow 上有個(gè)關(guān)于變長參數(shù)使用的問題。簡單地說,
在不確定方法需要處理的對象的數(shù)量時(shí)可以使用可變長參數(shù),會(huì)使得方法調(diào)用更簡單,無需手動(dòng)創(chuàng)建數(shù)組 new T[]{…}
原文地址:https://blog.csdn.net/qiuchengjia/article/details/52910888