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