在 Java 5 中提供了變長參數,允許在調用方法時傳入不定長度的參數。變長參數是 Java 的一個語法糖,本質上還是基于數組的實現:
void foo(String... args); void foo(String[] args);
//方法簽名 ([Ljava/lang/String;)V // public void foo(String[] args)
定義方法
在定義方法時,在最后一個形參后加上三點 …,就表示該形參可以接受多個參數值,多個參數值被當成數組傳入。上述定義有幾個要點需要注意:
-
可變參數只能作為函數的最后一個參數,但其前面可以有也可以沒有任何其他參數
-
由于可變參數必須是最后一個參數,所以一個函數最多只能有一個可變參數
-
Java的可變參數,會被編譯器轉型為一個數組
-
變長參數在編譯為字節碼后,在方法簽名中就是以數組形態出現的。這兩個方法的簽名是一致的,不能作為方法的重載。如果同時出現,是不能編譯通過的。可變參數可以兼容數組,反之則不成立
public void foo(String...varargs){} foo("arg1", "arg2", "arg3"); //上述過程和下面的調用是等價的 foo(new String[]{"arg1", "arg2", "arg3"});
-
J2SE 1.5 中新增了“泛型”的機制,可以在一定條件下把一個類型參數化。例如,可以在編寫一個類的時候,把一個方法的形參的類型用一個標識符(如T)來代表, 至于這個標識符到底表示什么類型,則在生成這個類的實例的時候再行指定。這一機制可以用來提供更充分的代碼重用和更嚴格的編譯時類型檢查。不過泛型機制卻不能和個數可變的形參配合使用。如果把一個能和不確定個實參相匹配的形參的類型,用一個標識符來代表,那么編譯器會給出一個 “generic array creation” 的錯誤
public class Varargs { public static void test(String... args) { for(String arg : args) {//當作數組用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){} }
可變參數方法的調用
調用可變參數方法,可以給出零到任意多個參數,編譯器會將可變參數轉化為一個數組。也可以直接傳遞一個數組,示例如下:
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個參數 test("a");//1個參數 test("a","b");//多個參數 test(new String[] {"a", "b", "c"});//直接傳遞數組 } }
方法重載
優先匹配固定參數
調用一個被重載的方法時,如果此調用既能夠和固定參數的重載方法匹配,也能夠與可變長參數的重載方法匹配,則選擇固定參數的方法:
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 優先匹配固定參數的重載方法 test();//version 1 } }
>匹配多個可變參數
調用一個被重載的方法時,如果此調用既能夠和兩個可變長參數的重載方法匹配,則編譯出錯:
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 } }
方法重寫
避免帶有變長參數的方法重載
即便編譯器可以按照優先匹配固定參數的方式確定具體的調用方法,但在閱讀代碼的依然容易掉入陷阱。要慎重考慮變長參數的方法重載。
別讓 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); }
讓編譯器知道這個null值是String類型的,編譯即可順利通過,也就減少了錯誤的發生。
覆寫變長方法也要循規蹈矩
package com; public class VarArgsTest2 { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub // 向上轉型 Base base = new Sub(); base.print("hello"); // 不轉型 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"); } }
第一個能編譯通過,這是為什么呢?事實上,base 對象把子類對象 sub 做了向上轉型,形參列表是由父類決定的,當然能通過。而看看子類直接調用的情況,這時編譯器看到子類覆寫了父類的 print 方法,因此肯定使用子類重新定義的 print 方法,盡管參數列表不匹配也不會跑到父類再去匹配下,因為找到了就不再找了,因此有了類型不匹配的錯誤。
這是個特例,覆寫的方法參數列表竟然可以與父類不相同,這違背了覆寫的定義,并且會引發莫名其妙的錯誤。
這里,總結下覆寫必須滿足的條件:
-
覆寫方法不能縮小訪問權限
-
參數列表必須與被覆寫方法相同(包括顯示形式)
-
返回類型必須與被覆寫方法的相同或是其子類
-
覆寫方法不能拋出新的異常,或者超出父類范圍的異常,但是可以拋出更少、更有限的異常,或者不拋出異常
可能出現的問題
使用 Object… 作為變長參數:
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[] 無法轉型為 Object[], 因而被當作一個單純的數組對象 ; Integer[] 可以轉型為 Object[], 可以作為一個對象數組。
反射方法調用時的注意事項
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(); } } }
上面的四個調用中,前兩個都會在運行時拋出 java.lang.IllegalArgumentException: wrong number of arguments 異常,后兩個則正常調用。
反射是運行時獲取的,在運行時看來,可變長參數和數組是一致的,因而方法簽名為:
//方法簽名 ([Ljava/lang/String;)V // public void foo(String[] varargs)
再來看一下 Method 對象的方法聲明:
Object invoke(Object obj, Object... args)
args 雖然是一個可變長度的參數,但是 args 的長度是受限于該方法對象代表的真實方法的參數列表長度的,而從運行時簽名來看,([Ljava/lang/String;)V 實際上只有一個形參,即 String[] varargs,因而 invoke(Object obj, Object… args) 中可變參數 args 的實參長度只能為1
//Object invoke(Object obj, Object... args) //String[] varArgs = new String[]{"arg1", "arg2"}; method.invoke(null, varArgs); //varArgs長度為2,錯誤 method.invoke(null, (Object[])varArgs); //將String[]轉換為Object[],長度為2的,錯誤 method.invoke(null, (Object)varArgs);//將整個String[] 轉為Object,長度為1,符合 method.invoke(null, new Object[]{varArgs});//Object[]長度為1,正確。上一個和這個是等價的
什么時候使用可變長參數?
Stack Overflow 上有個關于變長參數使用的問題。簡單地說,
在不確定方法需要處理的對象的數量時可以使用可變長參數,會使得方法調用更簡單,無需手動創建數組 new T[]{…}
原文地址:https://blog.csdn.net/qiuchengjia/article/details/52910888